CORDA-2876: Integrate the DJVM with the Corda Node. (#5633)

* * CORDA-2876: Migrate DJVM serialization modules into Corda.
* Pre-generate Corda classes for DJVM sandbox when node boots in production mode.
* Ensure that all DJVM test contract CorDapps are signed.
* Test examining attachments within DJVM sandbox.
* Test Contract.verify() using cryptographic verify function.
* Add test cases for more non-determinism in Contract.verify().
* Update node-driver to support testing nodes with DJVM support.
* Modify Node to allow alternative DJVM configurations for testing.
* Refactor DeterministicVerifierFactoryService for default use-case.
* Small whitespace and code-style refactors.
* Create and activate a DJVM execution profile for the Node.
* Revert making Verifier implement AutoCloseable.
* Allow the node to cache sandboxed Corda byte-code for reuse.
* Use updated Quasar agent that knows not to touch DJVM classloaders.
* Fix Quasar's package exclusions globs for DJVM.
* Deserialise LedgerTransaction into the sandbox for Contract.verify().
* Add the DJVM's serialisation modules to the Corda node.
* Update the node for the latest DJVM API, and preserve the ConstructorForDeserialization annotation on user contract classes.
* Add corda-dev to repositories while DJVM is SNAPSHOT.
* Migrate DJVM specialisation into AbstractNode's ServiceHubInternalImpl.
* Exclude sandbox.** and shaded djvm.** classes from Quasar agent.
* Add the corda-dev repository to :node for the deterministic runtime.
* Turn Verifier into an abstract base class that is specialised by BasicVerifier and DeterministicVerifier.
* Add the Corda deterministic libraries to the Node, and split the DJVM sandbox across two SandboxClassLoader instances.
* Add DJVM to contract verification path inside Corda Node.
* Minor lambda simplifications and removing unused import.
* CORDA-2871: Remove @CordaSerializable from LedgerTransaction.
* CORDA-2871: Add a callback to ServicesForResolution to allow the Node to modify a LedgerTransaction object.
* CORDA-2871: Refactor the contract verification code into a separate class,
 and allow LedgerTransaction to choose different Verifier objects.
* Update DJVM to use Corda 4.4-SNAPSHOT. (#95)
* CORDA-3330: Allow DJVM to preload / pregenerate classes from selected jars. (#92)
* Add support for SourceClassLoader.getResources() to DJVM.
* Allow a SandboxConfiguration to preload sandbox byte-code for all classes inside jars containing META-INF/DJVM-preload.
* CORDA-3309: Remove explicit try-catch in favour of UncaughtExceptionHandler. (#91)
* CORDA-3309: Install UncaughtExceptionHandler for DJVM tasks. (#88)
* Fix tests broken by Windows line endings. (#82)
* CORDA-3292: Reimplement ExecutionProfile as a data class. (#80)
* CORDA-2877: Refactor how we create child SandboxConfiguration objects. (#76)
* CORDA-2877: Load bytecode from a persistent cache to prevent repeated rewriting. (#75)
* Refactor byte-code cache to SandboxConfiguration instead of AnalysisConfiguration. We cannot "mix and match" byte-code generated by different sets of rules.
* CORDA-3137: Enhance annotation handling so that we can allow some annotations to be mapped into the sandbox without also needing to be stitched. (#72)
* CORDA-2871: Minor cosmetic fixes. (#69)
* CORDA-3218: Align DJVM with internal Corda Serialisation API. (#68)
* Ensure we get the latest SNAPSHOT of the serialisation code.
* CORDA-2871: Refactor SourceClassLoader to define source classes. (#66)
* Rewrite SourceClassLoader to support parent/child relationships.
* Revert catching TypNotPresebtException - it was a symptom of a bigger problem.
* Remove AutoCloseable from AnalysisConfiguration and SourceClassLoader.
* SourceClassLoader.getResource() must delegate to its parent first.
* CORDA-2871: Ensure ClassLoader.loadClass() throws ClassNotFoundException for all cases where the class cannot be found. (#64)
* CORDA-2871: Modify sandbox tasks to implement both java.Function and sandbox.Function (#62)
* Make TaskExecutors implement BiFunction to make them composable.
* Create ImportTask to wrap a java.Function inside a sandbox.Function.
* Add createExecutor() and createRawExecutor() APIs to SandboxClassLoader.
* Update serialization to use SandboxClassLoader.toSandboxClass().
* Remove a layer of lambdas from the serialisation code.
* Update SandboxExecutor and SandboxRawExecutor.
* Rename Executor to TaskFactory.
* Rename dangling executor -> taskFactory.
* CORDA-2871: Sanity fixes! (#63)
* Improve message for SandboxClassLoadingException.
* Fix serialisation API for using sandboxed environment.
* CORDA-3174: Extend serialisation to include InputStream and OpaqueBytesSubSequence. (#60)
* Update DJVM Example project for serialisation.
* Add serializers for InputStream and OpaqueBytesSubSequence.
* Support ZIP Inflater and CRC32 inside the sandbox.
* Allow the DJVM to wrap java.io.InputStream as sandbox.java.io.InputStream.
* Configure tests also to preserve @DeprecatedConstructorForDeserialization.
* CORDA-3174: Implement Corda serialization modules. (#59)
* Create DJVM serialization modules.
* Create test cases for Array<T>, List<T> and List<Array<T>>.
* Refactor SandboxPrimiveSerializer for all primitive types.
* Implement SandboxCollectionSerializer to support Collection types.
* Implement SandboxMapSerializer to support Map types.
* Attempt to fix infinite loop when computing Collection and Map fingerprints.
* Apply special handling when deserialising sandbox.java.lang.Character.
* Remap Java primitive types to sandbox Java object types to deter evolution.
* Use Class.getPackage().getName() to determine sandbox package name.
* Implement SandboxEnumSerializer to support Enum types.
* Implement SandboxPublicKeySerializer to support Java security keys.
* Add serialization projects to the composite example project.
* Implement serializers for BigInteger, BigDecimal, Currency and StringBuffer.
* Test that deserialising does not instantiate the untrusted user classes.
* Implement serializers for java.time.* types.
* Add serialiser for BitSet - currently disabled until BitSet itself is supported.
* Add serialisers for EnumSet and Class.
* Include support for EnumMap in the SandboxMapSerializer.
* Ensure the DJVM Example project's tests preserve @CordaSerializable.
* Add support for UUID as a primitive type.
* Use common abortReadOnly() method for declaring serialization as unsupported.
* Streamline the API for deserialising into the sandbox.
* Add preliminary support for deserialising X.509 certificates.
* Implement serializer for java.util.Optional.
* Refactor configuration of the sandbox serialization scheme.
* Add tests for deserialising arrays of basic types.
* Include method annotations in annotation stitching. This ensures that `@ConstructorForDeserialization` is not dropped.
* Enable test for SandboxBitSetSerializer.
* Enable tests for X.509 serializers.
* Implement serializers for ProtonJ primitive types.
* Serialize java.util.Date as a primitive type.
* Add the bintray Gradle plugin to the serialisation modules.
* Do not publish serialisation modules - they will become part of Corda itself.

* CORDA-2876: Only apply DJVM sources to Node Driver when devMode=true.

* Resolve DeteKT warnings.

* Require Node's JVM to set -Dnet.corda.djvm=true in order to enable DJVM.

* Enable DJVM for DemoBench nodes.

* Disable Quasar instrumentation verification for DemoBench nodes.

* Upgrade to DJVM 1.0-RC01.

* Try to modify DriverParameters in a more "ABI friendly" way.

* Refactor and simplify sandbox deserialisation of primitive objects.

* Review fixes.

* Update EvolutionSerializerFactory to handle sandboxed primitive boxed types.
This commit is contained in:
Chris Rankin 2019-11-05 13:44:18 +00:00 committed by Matthew Nesbit
parent f9d8084d44
commit f226ddc4f2
189 changed files with 7862 additions and 161 deletions

View File

@ -5895,10 +5895,8 @@ public abstract class net.corda.core.transactions.FullTransaction extends net.co
public abstract java.util.List<net.corda.core.contracts.StateAndRef<net.corda.core.contracts.ContractState>> getReferences()
##
@DoNotImplement
@CordaSerializable
public final class net.corda.core.transactions.LedgerTransaction extends net.corda.core.transactions.FullTransaction
public <init>(java.util.List<? extends net.corda.core.contracts.StateAndRef<? extends net.corda.core.contracts.ContractState>>, java.util.List<? extends net.corda.core.contracts.TransactionState<? extends net.corda.core.contracts.ContractState>>, java.util.List<? extends net.corda.core.contracts.CommandWithParties<? extends net.corda.core.contracts.CommandData>>, java.util.List<? extends net.corda.core.contracts.Attachment>, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt)
@DeprecatedConstructorForDeserialization
public <init>(java.util.List<? extends net.corda.core.contracts.StateAndRef<? extends net.corda.core.contracts.ContractState>>, java.util.List<? extends net.corda.core.contracts.TransactionState<? extends net.corda.core.contracts.ContractState>>, java.util.List<? extends net.corda.core.contracts.CommandWithParties<? extends net.corda.core.contracts.CommandData>>, java.util.List<? extends net.corda.core.contracts.Attachment>, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt, net.corda.core.node.NetworkParameters)
@NotNull
public final java.util.List<net.corda.core.contracts.Command<T>> commandsOfType(Class<T>)

View File

@ -76,6 +76,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")
@ -335,6 +336,7 @@ allprojects {
jcenter()
maven { url "$artifactory_contextUrl/corda-dependencies" }
maven { url 'https://repo.gradle.org/gradle/libs-releases' }
maven { url "$artifactory_contextUrl/corda-dev" }
}
configurations {
@ -481,11 +483,13 @@ bintrayConfig {
'corda-core',
'corda-core-deterministic',
'corda-deterministic-verifier',
'corda-deserializers-djvm',
'corda',
'corda-finance-workflows',
'corda-finance-contracts',
'corda-node',
'corda-node-api',
'corda-node-djvm',
'corda-test-common',
'corda-test-utils',
'corda-test-db',
@ -498,6 +502,7 @@ bintrayConfig {
'corda-tools-shell-cli',
'corda-serialization',
'corda-serialization-deterministic',
'corda-serialization-djvm',
'corda-tools-blob-inspector',
'corda-tools-explorer',
'corda-tools-network-bootstrapper',

View File

@ -13,11 +13,10 @@ java8MinUpdateVersion=171
platformVersion=5
guavaVersion=28.0-jre
# Quasar version to use with Java 8:
quasarVersion=0.7.10
quasarVersion=0.7.12_r3
quasarClassifier=jdk8
# Quasar version to use with Java 11:
quasarVersion11=0.8.0
# Specify a classifier for Java 11 built artifacts
quasarVersion11=0.8.0_r3_rc1
jdkClassifier11=jdk11
proguardVersion=6.1.1
bouncycastleVersion=1.60
@ -30,6 +29,7 @@ snakeYamlVersion=1.19
caffeineVersion=2.7.0
metricsVersion=4.1.0
metricsNewRelicVersion=1.1.1
djvmVersion=1.0-RC01
openSourceBranch=https://github.com/corda/corda/blob/master
openSourceSamplesBranch=https://github.com/corda/samples/blob/master
jolokiaAgentVersion=1.6.1

View File

@ -56,6 +56,7 @@ task patchCore(type: Zip, dependsOn: coreJarTask) {
archiveExtension = 'jar'
from(compileKotlin)
from(processResources)
from(zipTree(originalJar)) {
exclude 'net/corda/core/internal/*ToggleField*.class'
exclude 'net/corda/core/serialization/*SerializationFactory*.class'

View File

@ -8,7 +8,6 @@ import net.corda.core.crypto.*
import net.corda.core.crypto.SecureHash.Companion.zeroHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.accessLeafIndex
import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.IdentityService
import net.corda.core.serialization.deserialize
@ -307,37 +306,37 @@ class PartialMerkleTreeTest {
val pmt = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("1"), SecureHash.sha256("5"), SecureHash.sha256("0"), SecureHash.sha256("19")))
// First leaf.
assertEquals(0, pmt.accessLeafIndex(SecureHash.sha256("0")))
assertEquals(0, pmt.leafIndex(SecureHash.sha256("0")))
// Second leaf.
assertEquals(1, pmt.accessLeafIndex(SecureHash.sha256("1")))
assertEquals(1, pmt.leafIndex(SecureHash.sha256("1")))
// A random leaf.
assertEquals(5, pmt.accessLeafIndex(SecureHash.sha256("5")))
assertEquals(5, pmt.leafIndex(SecureHash.sha256("5")))
// The last leaf.
assertEquals(19, pmt.accessLeafIndex(SecureHash.sha256("19")))
assertEquals(19, pmt.leafIndex(SecureHash.sha256("19")))
// The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmt.accessLeafIndex(SecureHash.sha256("10")) }
assertFailsWith<MerkleTreeException> { pmt.leafIndex(SecureHash.sha256("10")) }
// The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree).
assertFailsWith<MerkleTreeException> { pmt.accessLeafIndex(SecureHash.sha256("30")) }
assertFailsWith<MerkleTreeException> { pmt.leafIndex(SecureHash.sha256("30")) }
val pmtFirstElementOnly = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("0")))
assertEquals(0, pmtFirstElementOnly.accessLeafIndex(SecureHash.sha256("0")))
assertEquals(0, pmtFirstElementOnly.leafIndex(SecureHash.sha256("0")))
// The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmtFirstElementOnly.accessLeafIndex(SecureHash.sha256("10")) }
assertFailsWith<MerkleTreeException> { pmtFirstElementOnly.leafIndex(SecureHash.sha256("10")) }
val pmtLastElementOnly = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("19")))
assertEquals(19, pmtLastElementOnly.accessLeafIndex(SecureHash.sha256("19")))
assertEquals(19, pmtLastElementOnly.leafIndex(SecureHash.sha256("19")))
// The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmtLastElementOnly.accessLeafIndex(SecureHash.sha256("10")) }
assertFailsWith<MerkleTreeException> { pmtLastElementOnly.leafIndex(SecureHash.sha256("10")) }
val pmtOneElement = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("5")))
assertEquals(5, pmtOneElement.accessLeafIndex(SecureHash.sha256("5")))
assertEquals(5, pmtOneElement.leafIndex(SecureHash.sha256("5")))
// The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmtOneElement.accessLeafIndex(SecureHash.sha256("10")) }
assertFailsWith<MerkleTreeException> { pmtOneElement.leafIndex(SecureHash.sha256("10")) }
val pmtAllIncluded = PartialMerkleTree.build(merkleTree, sampleLeaves)
for (i in 0..19) assertEquals(i, pmtAllIncluded.accessLeafIndex(SecureHash.sha256(i.toString())))
for (i in 0..19) assertEquals(i, pmtAllIncluded.leafIndex(SecureHash.sha256(i.toString())))
// The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree).
assertFailsWith<MerkleTreeException> { pmtAllIncluded.accessLeafIndex(SecureHash.sha256("30")) }
assertFailsWith<MerkleTreeException> { pmtAllIncluded.leafIndex(SecureHash.sha256("30")) }
}
@Test

View File

@ -1,6 +1,7 @@
package net.corda.core.crypto
import net.corda.core.CordaException
import net.corda.core.CordaInternal
import net.corda.core.KeepForDJVM
import net.corda.core.crypto.SecureHash.Companion.zeroHash
import net.corda.core.serialization.CordaSerializable
@ -169,8 +170,9 @@ class PartialMerkleTree(val root: PartialTree) {
* @return leaf-index of this component (starting from zero).
* @throws MerkleTreeException if the provided hash is not in the tree.
*/
@CordaInternal
@Throws(MerkleTreeException::class)
internal fun leafIndex(leaf: SecureHash): Int {
fun leafIndex(leaf: SecureHash): Int {
// Special handling if the tree consists of one node only.
if (root is PartialTree.IncludedLeaf && root.hash == leaf) return 0
val flagPath = mutableListOf<Boolean>()

View File

@ -1,12 +1,15 @@
package net.corda.core.internal
import net.corda.core.DeleteForDJVM
import net.corda.core.KeepForDJVM
import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.*
import net.corda.core.contracts.TransactionVerificationException.TransactionContractConflictException
import net.corda.core.crypto.SecureHash
import net.corda.core.internal.rules.StateContractValidationEnforcementRule
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.contextLogger
import java.util.function.Function
@DeleteForDJVM
interface TransactionVerifierServiceInternal {
@ -25,15 +28,13 @@ 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.
*
* @param inputVersions A map linking each contract class name to the advertised version of the JAR that defines it. Used for downgrade protection.
*/
class Verifier(val ltx: LedgerTransaction, private val transactionClassLoader: ClassLoader) {
abstract class Verifier(val ltx: LedgerTransaction, protected val transactionClassLoader: ClassLoader) {
private val inputStates: List<TransactionState<*>> = ltx.inputs.map { it.state }
private val allStates: List<TransactionState<*>> = inputStates + ltx.references.map { it.state } + ltx.outputs
companion object {
private val logger = contextLogger()
val logger = contextLogger()
}
/**
@ -140,7 +141,7 @@ class Verifier(val ltx: LedgerTransaction, private val transactionClassLoader: C
.withIndex()
.filter { it.value.encumbrance != null }
.map { Pair(it.index, it.value.encumbrance!!) }
if (!statesAndEncumbrance.isEmpty()) {
if (statesAndEncumbrance.isNotEmpty()) {
checkBidirectionalOutputEncumbrances(statesAndEncumbrance)
checkNotariesOutputEncumbrance(statesAndEncumbrance)
}
@ -343,28 +344,56 @@ class Verifier(val ltx: LedgerTransaction, private val transactionClassLoader: C
}
}
/**
* Placeholder function for the contract verification logic.
*/
abstract fun verifyContracts()
}
class BasicVerifier(ltx: LedgerTransaction, transactionClassLoader: ClassLoader) : Verifier(ltx, transactionClassLoader) {
/**
* Check the transaction is contract-valid by running the verify() for each input and output state contract.
* If any contract fails to verify, the whole transaction is considered to be invalid.
*
* Note: Reference states are not verified.
*/
private fun verifyContracts() {
// Loads the contract class from the transactionClassLoader.
fun contractClassFor(className: ContractClassName) = try {
transactionClassLoader.loadClass(className).asSubclass(Contract::class.java)
} catch (e: Exception) {
throw TransactionVerificationException.ContractCreationError(ltx.id, className, e)
override fun verifyContracts() {
try {
ContractVerifier(transactionClassLoader).apply(ltx)
} catch (e: TransactionVerificationException.ContractRejection) {
logger.error("Error validating transaction ${ltx.id}.", e.cause)
throw e
}
}
}
val contractClasses: Map<ContractClassName, Class<out Contract>> = (inputStates + ltx.outputs)
.map { it.contract }
.toSet()
.map { contract -> contract to contractClassFor(contract) }
.toMap()
/**
* Verify all of the contracts on the given [LedgerTransaction].
*/
@Suppress("TooGenericExceptionCaught")
@KeepForDJVM
class ContractVerifier(private val transactionClassLoader: ClassLoader) : Function<LedgerTransaction, Unit> {
// This constructor is used inside the DJVM's sandbox.
@Suppress("unused")
constructor() : this(ClassLoader.getSystemClassLoader())
val contractInstances: List<Contract> = contractClasses.map { (contractClassName, contractClass) ->
// Loads the contract class from the transactionClassLoader.
private fun createContractClass(id: SecureHash, contractClassName: ContractClassName): Class<out Contract> {
return try {
Class.forName(contractClassName, false, transactionClassLoader).asSubclass(Contract::class.java)
} catch (e: Exception) {
throw TransactionVerificationException.ContractCreationError(id, contractClassName, e)
}
}
override fun apply(ltx: LedgerTransaction) {
val contractClassNames = (ltx.inputs.map(StateAndRef<ContractState>::state) + ltx.outputs)
.map(TransactionState<*>::contract)
.toSet()
contractClassNames.associateBy(
{ it }, { createContractClass(ltx.id, it) }
).map { (contractClassName, contractClass) ->
try {
/**
* This function must execute with the DJVM's sandbox, which does not
@ -377,13 +406,10 @@ class Verifier(val ltx: LedgerTransaction, private val transactionClassLoader: C
} catch (e: Exception) {
throw TransactionVerificationException.ContractCreationError(ltx.id, contractClassName, e)
}
}
contractInstances.forEach { contract ->
}.forEach { contract ->
try {
contract.verify(ltx)
} catch (e: Exception) {
logger.error("Error validating transaction ${ltx.id}.", e)
throw TransactionVerificationException.ContractRejection(ltx.id, contract, e)
}
}

View File

@ -10,6 +10,7 @@ import net.corda.core.flows.ContractUpgradeFlow
import net.corda.core.node.services.*
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.transactions.FilteredTransaction
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import java.security.PublicKey
@ -71,6 +72,12 @@ interface ServicesForResolution {
*/
@Throws(TransactionResolutionException::class, AttachmentResolutionException::class)
fun loadContractAttachment(stateRef: StateRef): Attachment
/**
* Provides a callback for the Node to customise the [LedgerTransaction].
*/
@JvmDefault
fun specialise(ltx: LedgerTransaction): LedgerTransaction = ltx
}
/**

View File

@ -1,6 +1,8 @@
@file:KeepForDJVM
package net.corda.core.node.services
import net.corda.core.DoNotImplement
import net.corda.core.KeepForDJVM
import net.corda.core.contracts.Attachment
import net.corda.core.crypto.SecureHash
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION

View File

@ -19,7 +19,6 @@ import java.io.IOException
import java.io.InputStream
import java.net.*
import java.util.*
import java.util.jar.JarInputStream
/**
* A custom ClassLoader that knows how to load classes from a set of attachments. The attachments themselves only
@ -318,14 +317,14 @@ object AttachmentsClassLoaderBuilder {
isAttachmentTrusted: (Attachment) -> Boolean,
parent: ClassLoader = ClassLoader.getSystemClassLoader(),
block: (ClassLoader) -> T): T {
val attachmentIds = attachments.map { it.id }.toSet()
val attachmentIds = attachments.map(Attachment::id).toSet()
val serializationContext = cache.computeIfAbsent(Key(attachmentIds, params)) {
// Create classloader and load serializers, whitelisted classes
val transactionClassLoader = AttachmentsClassLoader(attachments, params, txId, isAttachmentTrusted, parent)
val serializers = createInstancesOfClassesImplementing(transactionClassLoader, SerializationCustomSerializer::class.java)
val whitelistedClasses = ServiceLoader.load(SerializationWhitelist::class.java, transactionClassLoader)
.flatMap { it.whitelist }
.flatMap(SerializationWhitelist::whitelist)
.toList()
// Create a new serializationContext for the current transaction. In this context we will forbid

View File

@ -3,18 +3,32 @@ package net.corda.core.transactions
import net.corda.core.CordaInternal
import net.corda.core.KeepForDJVM
import net.corda.core.StubOutForDJVM
import net.corda.core.contracts.*
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.Command
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.CommandWithParties
import net.corda.core.contracts.ComponentGroupEnum
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.PrivacySalt
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.contracts.TransactionState
import net.corda.core.contracts.TransactionVerificationException
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.Party
import net.corda.core.internal.*
import net.corda.core.internal.BasicVerifier
import net.corda.core.internal.SerializedStateAndRef
import net.corda.core.internal.Verifier
import net.corda.core.internal.castIfPossible
import net.corda.core.internal.deserialiseCommands
import net.corda.core.internal.deserialiseComponentGroup
import net.corda.core.internal.isUploaderTrusted
import net.corda.core.internal.uncheckedCast
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.ConstructorForDeserialization
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
import net.corda.core.utilities.contextLogger
import java.util.*
import java.util.Collections.unmodifiableList
import java.util.function.Predicate
/**
@ -39,10 +53,9 @@ import java.util.function.Predicate
*
* [LedgerTransaction]s should never be instantiated directly from client code, but rather via WireTransaction.toLedgerTransaction
*/
@Suppress("LongParameterList")
@KeepForDJVM
@CordaSerializable
class LedgerTransaction
@ConstructorForDeserialization
private constructor(
// DOCSTART 1
/** The resolved input states which will be consumed/invalidated by the execution of this transaction. */
@ -67,23 +80,35 @@ private constructor(
*/
override val networkParameters: NetworkParameters?,
/** Referenced states, which are like inputs but won't be consumed. */
override val references: List<StateAndRef<ContractState>>
override val references: List<StateAndRef<ContractState>>,
//DOCEND 1
private val componentGroups: List<ComponentGroup>?,
private val serializedInputs: List<SerializedStateAndRef>?,
private val serializedReferences: List<SerializedStateAndRef>?,
private val isAttachmentTrusted: (Attachment) -> Boolean,
private val verifierFactory: (LedgerTransaction, ClassLoader) -> Verifier
) : FullTransaction() {
// These are not part of the c'tor above as that defines LedgerTransaction's serialisation format
private var componentGroups: List<ComponentGroup>? = null
private var serializedInputs: List<SerializedStateAndRef>? = null
private var serializedReferences: List<SerializedStateAndRef>? = null
private var isAttachmentTrusted: (Attachment) -> Boolean = { it.isUploaderTrusted() }
init {
if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" }
checkNotaryWhitelisted()
}
@KeepForDJVM
companion object {
private val logger = contextLogger()
private fun <T> protect(list: List<T>?): List<T>? {
return list?.run {
if (isEmpty()) {
emptyList()
} else {
unmodifiableList(this)
}
}
}
@CordaInternal
internal fun create(
inputs: List<StateAndRef<ContractState>>,
@ -101,12 +126,58 @@ private constructor(
serializedReferences: List<SerializedStateAndRef>? = null,
isAttachmentTrusted: (Attachment) -> Boolean
): LedgerTransaction {
return LedgerTransaction(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references).apply {
this.componentGroups = componentGroups
this.serializedInputs = serializedInputs
this.serializedReferences = serializedReferences
this.isAttachmentTrusted = isAttachmentTrusted
return LedgerTransaction(
inputs = inputs,
outputs = outputs,
commands = commands,
attachments = attachments,
id = id,
notary = notary,
timeWindow = timeWindow,
privacySalt = privacySalt,
networkParameters = networkParameters,
references = references,
componentGroups = protect(componentGroups),
serializedInputs = protect(serializedInputs),
serializedReferences = protect(serializedReferences),
isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = ::BasicVerifier
)
}
/**
* This factory function will create an instance of [LedgerTransaction]
* that will be used inside the DJVM sandbox.
*/
@CordaInternal
fun createForSandbox(
inputs: List<StateAndRef<ContractState>>,
outputs: List<TransactionState<ContractState>>,
commands: List<CommandWithParties<CommandData>>,
attachments: List<Attachment>,
id: SecureHash,
notary: Party?,
timeWindow: TimeWindow?,
privacySalt: PrivacySalt,
networkParameters: NetworkParameters,
references: List<StateAndRef<ContractState>>): LedgerTransaction {
return LedgerTransaction(
inputs = inputs,
outputs = outputs,
commands = commands,
attachments = attachments,
id = id,
notary = notary,
timeWindow = timeWindow,
privacySalt = privacySalt,
networkParameters = networkParameters,
references = references,
componentGroups = null,
serializedInputs = null,
serializedReferences = null,
isAttachmentTrusted = { true },
verifierFactory = ::BasicVerifier
)
}
}
@ -144,17 +215,48 @@ private constructor(
// Switch thread local deserialization context to using a cached attachments classloader. This classloader enforces various rules
// like no-overlap, package namespace ownership and (in future) deterministic Java.
return AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(
this.attachments + extraAttachments,
attachments + extraAttachments,
getParamsWithGoo(),
id,
isAttachmentTrusted = isAttachmentTrusted) { transactionClassLoader ->
// Create a copy of the outer LedgerTransaction which deserializes all fields inside the [transactionClassLoader].
// Only the copy will be used for verification, and the outer shell will be discarded.
// This artifice is required to preserve backwards compatibility.
Verifier(createLtxForVerification(), transactionClassLoader)
verifierFactory(createLtxForVerification(), transactionClassLoader)
}
}
/**
* Pass all of this [LedgerTransaction] object's serialized state to a [transformer] function.
*/
@CordaInternal
fun <T> transform(transformer: (List<ComponentGroup>, List<SerializedStateAndRef>, List<SerializedStateAndRef>) -> T): T {
return transformer(componentGroups ?: emptyList(), serializedInputs ?: emptyList(), serializedReferences ?: emptyList())
}
/**
* We need a way to customise transaction verification inside the
* Node without changing either the wire format or any public APIs.
*/
@CordaInternal
fun specialise(alternateVerifier: (LedgerTransaction, ClassLoader) -> Verifier): LedgerTransaction = LedgerTransaction(
inputs = inputs,
outputs = outputs,
commands = commands,
attachments = attachments,
id = id,
notary = notary,
timeWindow = timeWindow,
privacySalt = privacySalt,
networkParameters = networkParameters,
references = references,
componentGroups = componentGroups,
serializedInputs = serializedInputs,
serializedReferences = serializedReferences,
isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = alternateVerifier
)
// Read network parameters with backwards compatibility goo.
private fun getParamsWithGoo(): NetworkParameters {
var params = networkParameters
@ -213,7 +315,12 @@ private constructor(
timeWindow = this.timeWindow,
privacySalt = this.privacySalt,
networkParameters = this.networkParameters,
references = deserializedReferences
references = deserializedReferences,
componentGroups = componentGroups,
serializedInputs = serializedInputs,
serializedReferences = serializedReferences,
isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = verifierFactory
)
} else {
// This branch is only present for backwards compatibility.
@ -582,10 +689,25 @@ private constructor(
notary: Party?,
timeWindow: TimeWindow?,
privacySalt: PrivacySalt
) : this(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, null, emptyList())
) : this(
inputs = inputs,
outputs = outputs,
commands = commands,
attachments = attachments,
id = id,
notary = notary,
timeWindow = timeWindow,
privacySalt = privacySalt,
networkParameters = null,
references = emptyList(),
componentGroups = null,
serializedInputs = null,
serializedReferences = null,
isAttachmentTrusted = { it.isUploaderTrusted() },
verifierFactory = ::BasicVerifier
)
@Deprecated("LedgerTransaction should not be created directly, use WireTransaction.toLedgerTransaction instead.")
@DeprecatedConstructorForDeserialization(1)
constructor(
inputs: List<StateAndRef<ContractState>>,
outputs: List<TransactionState<ContractState>>,
@ -596,7 +718,23 @@ private constructor(
timeWindow: TimeWindow?,
privacySalt: PrivacySalt,
networkParameters: NetworkParameters
) : this(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, emptyList())
) : this(
inputs = inputs,
outputs = outputs,
commands = commands,
attachments = attachments,
id = id,
notary = notary,
timeWindow = timeWindow,
privacySalt = privacySalt,
networkParameters = networkParameters,
references = emptyList(),
componentGroups = null,
serializedInputs = null,
serializedReferences = null,
isAttachmentTrusted = { it.isUploaderTrusted() },
verifierFactory = ::BasicVerifier
)
@Deprecated("LedgerTransactions should not be created directly, use WireTransaction.toLedgerTransaction instead.")
fun copy(inputs: List<StateAndRef<ContractState>>,
@ -618,7 +756,12 @@ private constructor(
timeWindow = timeWindow,
privacySalt = privacySalt,
networkParameters = networkParameters,
references = references
references = references,
componentGroups = componentGroups,
serializedInputs = serializedInputs,
serializedReferences = serializedReferences,
isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = verifierFactory
)
}
@ -643,7 +786,12 @@ private constructor(
timeWindow = timeWindow,
privacySalt = privacySalt,
networkParameters = networkParameters,
references = references
references = references,
componentGroups = componentGroups,
serializedInputs = serializedInputs,
serializedReferences = serializedReferences,
isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = verifierFactory
)
}
}

View File

@ -70,8 +70,8 @@ open class TransactionBuilder(
private fun defaultLockId() = (Strand.currentStrand() as? FlowStateMachine<*>)?.id?.uuid ?: UUID.randomUUID()
private val log = contextLogger()
private val ID_PATTERN = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"
private val FQCP = Pattern.compile("$ID_PATTERN(/$ID_PATTERN)+")
private const val ID_PATTERN = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"
private val FQCP: Pattern = Pattern.compile("$ID_PATTERN(/$ID_PATTERN)+")
private fun isValidJavaClass(identifier: String) = FQCP.matcher(identifier).matches()
}

View File

@ -99,7 +99,8 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
@DeleteForDJVM
fun toLedgerTransaction(services: ServicesForResolution): LedgerTransaction {
return toLedgerTransactionInternal(
return services.specialise(
toLedgerTransactionInternal(
resolveIdentity = { services.identityService.partyFromKey(it) },
resolveAttachment = { services.attachments.openAttachment(it) },
resolveStateRefAsSerialized = { resolveStateRefBinaryComponent(it, services) },
@ -110,6 +111,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
// `as?` is used due to [MockServices] not implementing [ServiceHubCoreInternal]
isAttachmentTrusted = { (services as? ServiceHubCoreInternal)?.attachmentTrustCalculator?.calculate(it) ?: true }
)
)
}
// Helper for deprecated toLedgerTransaction
@ -129,7 +131,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
* @throws AttachmentResolutionException if a required attachment was not found using [resolveAttachment].
* @throws TransactionResolutionException if an input was not found not using [resolveStateRef].
*/
@Deprecated("Use toLedgerTransaction(ServicesForTransaction) instead")
@Deprecated("Use toLedgerTransaction(ServicesForResolution) instead")
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(
resolveIdentity: (PublicKey) -> Party?,

View File

@ -1,7 +1,6 @@
package net.corda.core.internal
import net.corda.core.contracts.*
import net.corda.core.crypto.PartialMerkleTree
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.node.NetworkParameters
@ -37,5 +36,3 @@ fun createLedgerTransaction(
fun createContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable) = TransactionVerificationException.ContractCreationError(txId, contractClass, cause)
fun createContractRejection(txId: SecureHash, contract: Contract, cause: Throwable) = TransactionVerificationException.ContractRejection(txId, contract, cause)
fun PartialMerkleTree.accessLeafIndex(id: SecureHash) = this.leafIndex(id)

View File

@ -31,6 +31,13 @@ apply plugin: 'com.jfrog.artifactory'
description 'Corda node modules'
repositories {
// Extra repository for the deterministic-rt JAR.
maven {
url "$artifactory_contextUrl/corda-dev"
}
}
//noinspection GroovyAssignabilityCheck
configurations {
integrationTestCompile.extendsFrom testCompile
@ -38,6 +45,11 @@ configurations {
slowIntegrationTestCompile.extendsFrom testCompile
slowIntegrationTestRuntimeOnly.extendsFrom testRuntimeOnly
jdkRt.resolutionStrategy {
cacheChangingModulesFor 0, 'seconds'
}
deterministic
}
sourceSets {
@ -156,6 +168,19 @@ 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.djvm:corda-djvm:$djvm_version"
compile project(':serialization-djvm')
compile(project(':node:djvm')) {
transitive = false
}
jdkRt "net.corda:deterministic-rt:latest.integration"
deterministic project(path: ':core-deterministic', configuration: 'deterministicArtifacts')
deterministic project(path: ':serialization-deterministic', configuration: 'deterministicArtifacts')
deterministic project(':serialization-djvm:deserializers')
deterministic project(':node:djvm')
deterministic "org.slf4j:slf4j-nop:$slf4j_version"
testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
testImplementation "junit:junit:$junit_version"
@ -245,9 +270,12 @@ tasks.withType(JavaCompile) {
}
tasks.withType(Test) {
if (JavaVersion.current() == JavaVersion.VERSION_11)
if (JavaVersion.current() == JavaVersion.VERSION_11) {
jvmArgs '-Djdk.attach.allowAttachSelf=true'
}
systemProperty 'deterministic-rt.path', configurations.jdkRt.asPath
systemProperty 'deterministic-sources.path', configurations.deterministic.asPath
}
task integrationTest(type: Test) {
testClassesDirs = sourceSets.integrationTest.output.classesDirs
@ -263,6 +291,9 @@ task slowIntegrationTest(type: Test) {
// quasar exclusions upon agent code instrumentation at run-time
quasar {
excludeClassLoaders.addAll(
'net.corda.djvm.**'
)
excludePackages.addAll(
"antlr**",
"com.codahale**",
@ -271,10 +302,12 @@ quasar {
"com.google.**",
"com.lmax.**",
"com.zaxxer.**",
"djvm**",
"net.bytebuddy**",
"io.github.classgraph**",
"io.netty*",
"liquibase**",
"net.corda.djvm**",
"net.i2p.crypto.**",
"nonapi.io.github.classgraph.**",
"org.apiguardian.**",
@ -292,6 +325,10 @@ quasar {
jar {
baseName 'corda-node'
manifest {
attributes('Corda-Deterministic-Runtime': configurations.jdkRt.singleFile.name)
attributes('Corda-Deterministic-Classpath': configurations.deterministic.collect { it.name }.join(' '))
}
}
publish {

View File

@ -8,6 +8,8 @@ apply plugin: 'com.jfrog.artifactory'
description 'Corda standalone node'
evaluationDependsOn(':node')
configurations {
runtimeArtifacts.extendsFrom runtimeClasspath
capsuleRuntime
@ -34,28 +36,64 @@ capsule {
version capsule_version
}
task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').tasks.jar) {
def nodeProject = project(':node')
task buildCordaJAR(type: FatCapsule, dependsOn: [
nodeProject.tasks.jar,
project(':core-deterministic').tasks.assemble,
project(':serialization-deterministic').tasks.assemble
]) {
applicationClass 'net.corda.node.Corda'
archiveBaseName = 'corda'
archiveVersion = corda_release_version
archiveClassifier = jdkClassifier
archiveName = archiveFileName.get()
applicationSource = files(
project(':node').configurations.runtimeClasspath,
project(':node').tasks.jar,
project(':node').buildDir.toString() + '/resources/main/reference.conf',
nodeProject.configurations.runtimeClasspath,
nodeProject.tasks.jar,
nodeProject.buildDir.toString() + '/resources/main/reference.conf',
"$rootDir/config/dev/log4j2.xml",
'NOTICE' // Copy CDDL notice
)
from configurations.capsuleRuntime.files.collect { zipTree(it) }
with jar
// The DJVM will share most of its dependencies with the node, but any extra ones that it needs
// are listed in the node's "deterministic" configuration and copied into a djvm subdirectory.
//
// Gradle may not resolve exactly the same transitive dependencies for both the runtimeClasspath
// and deterministic configurations - specifically, the artifacts' version numbers may differ slightly.
// And so we map the files by the resolved ModuleIdentifier objects instead, which just contain an
// artifact's group and name.
def cordaResolved = nodeProject.configurations['runtimeClasspath'].resolvedConfiguration.resolvedArtifacts.collectEntries {
[ (it.moduleVersion.id.module):it.file ]
}
def deterministicResolved = nodeProject.configurations['deterministic'].resolvedConfiguration.resolvedArtifacts.collectEntries {
[ (it.moduleVersion.id.module):it.file ]
}
def resolvedDifferences = deterministicResolved.keySet() - cordaResolved.keySet()
cordaResolved.keySet().retainAll(deterministicResolved.keySet() - resolvedDifferences)
deterministicResolved.keySet().retainAll(resolvedDifferences)
manifest {
// These are the dependencies that the deterministic Corda libraries share with Corda.
attributes('Corda-DJVM-Dependencies': cordaResolved.values().collect { it.name }.join(' '))
}
into('djvm') {
from nodeProject.configurations['jdkRt'].singleFile
from deterministicResolved.values()
fileMode = 0444
}
capsuleManifest {
applicationVersion = corda_release_version
applicationId = "net.corda.node.Corda"
// See experimental/quasar-hook/README.md for how to generate.
def quasarExcludeExpression = "x(antlr**;bftsmart**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;junit**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**)"
javaAgents = quasar_classifier == null ? ["quasar-core-${quasar_version}.jar=${quasarExcludeExpression}"] : ["quasar-core-${quasar_version}-${quasar_classifier}.jar=${quasarExcludeExpression}"]
def quasarExcludeExpression = "x(antlr**;bftsmart**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;kotlin**;net.corda.djvm**;djvm**;net.bytebuddy**;net.i2p**;org.apache**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**)"
def quasarClassLoaderExclusion = "l(net.corda.djvm.**)"
javaAgents = quasar_classifier == null ? ["quasar-core-${quasar_version}.jar=${quasarExcludeExpression}${quasarClassLoaderExclusion}"] : ["quasar-core-${quasar_version}-${quasar_classifier}.jar=${quasarExcludeExpression}${quasarClassLoaderExclusion}"]
systemProperties['visualvm.display.name'] = 'Corda'
if (JavaVersion.current() == JavaVersion.VERSION_1_8) {
minJavaVersion = '1.8.0'
@ -70,10 +108,11 @@ task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').tasks.jar) {
//
// If you change these flags, please also update Driver.kt
jvmArgs = ['-Xmx512m', '-XX:+UseG1GC']
if (JavaVersion.current() == JavaVersion.VERSION_11)
if (JavaVersion.current() == JavaVersion.VERSION_11) {
jvmArgs += ['-Djdk.attach.allowAttachSelf=true']
}
}
}
assemble.dependsOn buildCordaJAR

View File

@ -6,12 +6,22 @@ import com.typesafe.config.*;
import sun.misc.Signal;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import java.util.stream.Stream;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static java.util.stream.Collectors.toMap;
public class CordaCaplet extends Capsule {
private static final String DJVM_DIR ="djvm";
private Config nodeConfig = null;
private String baseDir = null;
@ -79,10 +89,76 @@ 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 {
try {
Path sourceDir = appDir().resolve(DJVM_DIR);
if (Files.isDirectory(sourceDir)) {
installCordaDependenciesForDJVM(sourceDir, djvmDir);
installTransitiveDependenciesForDJVM(appDir(), djvmDir);
}
} catch (IOException e) {
log(LOG_VERBOSE, "Failed to populate directory " + djvmDir.toAbsolutePath());
log(LOG_VERBOSE, e);
}
}
}
private void installCordaDependenciesForDJVM(Path sourceDir, Path targetDir) throws IOException {
try (DirectoryStream<Path> directory = Files.newDirectoryStream(sourceDir, file -> Files.isRegularFile(file))) {
for (Path sourceFile : directory) {
Path targetFile = targetDir.resolve(sourceFile.getFileName());
installFile(sourceFile, targetFile);
}
}
}
private void installTransitiveDependenciesForDJVM(Path sourceDir, Path targetDir) throws IOException {
Manifest manifest = getManifest();
String[] transitives = manifest.getMainAttributes().getValue("Corda-DJVM-Dependencies").split("\\s++", 0);
for (String transitive : transitives) {
Path source = sourceDir.resolve(transitive);
if (Files.isRegularFile(source)) {
installFile(source, targetDir.resolve(transitive));
}
}
}
private Manifest getManifest() throws IOException {
URL capsule = getClass().getProtectionDomain().getCodeSource().getLocation();
try (JarInputStream jar = new JarInputStream(capsule.openStream())) {
return jar.getManifest();
}
}
private void installFile(Path source, Path target) {
try {
// Forcibly reinstall this dependency.
Files.deleteIfExists(target);
Files.createSymbolicLink(target, source);
} catch (UnsupportedOperationException | IOException e) {
copyFile(source, target);
}
}
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);
}
}
@Override
protected ProcessBuilder prelaunch(List<String> jvmArgs, List<String> args) {
checkJavaVersion();
nodeConfig = parseConfigFile(args);
installDJVM();
return super.prelaunch(jvmArgs, args);
}
@ -155,10 +231,13 @@ public class CordaCaplet extends Capsule {
// Add system properties, if specified, from the config.
Map<String, String> systemProps = new LinkedHashMap<>((Map<String, String>) super.attribute(attr));
try {
Config overrideSystemProps = nodeConfig.getConfig("systemProperties");
Map<String, ?> overrideSystemProps = nodeConfig.getConfig("systemProperties").entrySet().stream()
.map(Property::create)
.filter(Property::isValid)
.collect(toMap(Property::getKey, Property::getValue));
log(LOG_VERBOSE, "Configured system properties = " + overrideSystemProps);
for (Map.Entry<String, ConfigValue> entry : overrideSystemProps.entrySet()) {
systemProps.put(entry.getKey(), entry.getValue().unwrapped().toString());
for (Map.Entry<String, ?> entry : overrideSystemProps.entrySet()) {
systemProps.put(entry.getKey(), entry.getValue().toString());
}
} catch (ConfigException.Missing e) {
// Ignore since it's ok to be Missing. Other errors would be unexpected.
@ -237,4 +316,33 @@ public class CordaCaplet extends Capsule {
private Boolean isJAR(File file) {
return file.getName().toLowerCase().endsWith(".jar");
}
/**
* Helper class so that we can parse the "systemProperties" element of node.conf.
*/
private static class Property {
private final List<String> path;
private final Object value;
Property(List<String> path, Object value) {
this.path = path;
this.value = value;
}
boolean isValid() {
return path.size() == 1;
}
String getKey() {
return path.get(0);
}
Object getValue() {
return value;
}
static Property create(Map.Entry<String, ConfigValue> entry) {
return new Property(ConfigUtil.splitPath(entry.getKey()), entry.getValue().unwrapped());
}
}
}

24
node/djvm/build.gradle Normal file
View File

@ -0,0 +1,24 @@
apply from: '../../deterministic.gradle'
apply plugin: 'com.jfrog.artifactory'
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'java-library'
description 'Internal Corda Node modules for deterministic contract verification.'
dependencies {
api 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
api project(path: ':core-deterministic', configuration: 'deterministicArtifacts')
api project(path: ':serialization-deterministic', configuration: 'deterministicArtifacts')
}
jar {
archiveBaseName = 'corda-node-djvm'
manifest {
attributes('Automatic-Module-Name': 'net.corda.node.djvm')
attributes('Sealed': true)
}
}
publish {
name jar.archiveBaseName.get()
}

View File

@ -0,0 +1,62 @@
@file:JvmName("AttachmentConstants")
package net.corda.node.djvm
import net.corda.core.contracts.Attachment
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import java.io.InputStream
import java.security.PublicKey
import java.util.Collections.unmodifiableList
import java.util.function.Function
private const val SIGNER_KEYS_IDX = 0
private const val SIZE_IDX = 1
private const val ID_IDX = 2
private const val ATTACHMENT_IDX = 3
private const val STREAMER_IDX = 4
class AttachmentBuilder : Function<Array<Any>?, List<Attachment>?> {
private val attachments = mutableListOf<Attachment>()
private fun <T> unmodifiable(list: List<T>): List<T> {
return if (list.isEmpty()) {
emptyList()
} else {
unmodifiableList(list)
}
}
override fun apply(inputs: Array<Any>?): List<Attachment>? {
return if (inputs == null) {
unmodifiable(attachments)
} else {
@Suppress("unchecked_cast")
attachments.add(SandboxAttachment(
signerKeys = inputs[SIGNER_KEYS_IDX] as List<PublicKey>,
size = inputs[SIZE_IDX] as Int,
id = inputs[ID_IDX] as SecureHash,
attachment = inputs[ATTACHMENT_IDX],
streamer = inputs[STREAMER_IDX] as Function<in Any, out InputStream>
))
null
}
}
}
/**
* This represents an [Attachment] from within the sandbox.
*/
class SandboxAttachment(
override val signerKeys: List<PublicKey>,
override val size: Int,
override val id: SecureHash,
private val attachment: Any,
private val streamer: Function<Any, out InputStream>
) : Attachment {
@Suppress("OverridingDeprecatedMember")
override val signers: List<Party> = emptyList()
override fun open(): InputStream {
return streamer.apply(attachment)
}
}

View File

@ -0,0 +1,40 @@
package net.corda.node.djvm
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.CommandWithParties
import net.corda.core.internal.lazyMapped
import java.security.PublicKey
import java.util.function.Function
class CommandBuilder : Function<Array<Any?>, List<CommandWithParties<CommandData>>> {
@Suppress("unchecked_cast")
override fun apply(inputs: Array<Any?>): List<CommandWithParties<CommandData>> {
val signers = inputs[0] as? List<List<PublicKey>> ?: emptyList()
val commandsData = inputs[1] as? List<CommandData> ?: emptyList()
val partialMerkleLeafIndices = inputs[2] as? IntArray
/**
* This logic has been lovingly reproduced from [net.corda.core.internal.deserialiseCommands].
*/
return if (partialMerkleLeafIndices != null) {
check(commandsData.size <= signers.size) {
"Invalid Transaction. Fewer Signers (${signers.size}) than CommandData (${commandsData.size}) objects"
}
if (partialMerkleLeafIndices.isNotEmpty()) {
check(partialMerkleLeafIndices.max()!! < signers.size) {
"Invalid Transaction. A command with no corresponding signer detected"
}
}
commandsData.lazyMapped { commandData, index ->
CommandWithParties(signers[partialMerkleLeafIndices[index]], emptyList(), commandData)
}
} else {
check(commandsData.size == signers.size) {
"Invalid Transaction. Sizes of CommandData (${commandsData.size}) and Signers (${signers.size}) do not match"
}
commandsData.lazyMapped { commandData, index ->
CommandWithParties(signers[index], emptyList(), commandData)
}
}
}
}

View File

@ -0,0 +1,24 @@
package net.corda.node.djvm
import net.corda.core.contracts.ComponentGroupEnum
import net.corda.core.internal.TransactionDeserialisationException
import net.corda.core.internal.lazyMapped
import net.corda.core.utilities.OpaqueBytes
import java.util.function.Function
class ComponentBuilder : Function<Array<Any?>, List<*>> {
@Suppress("unchecked_cast", "TooGenericExceptionCaught")
override fun apply(inputs: Array<Any?>): List<*> {
val deserializer = inputs[0] as Function<in Any?, out Any?>
val groupType = inputs[1] as ComponentGroupEnum
val components = (inputs[2] as Array<ByteArray>).map(::OpaqueBytes)
return components.lazyMapped { component, index ->
try {
deserializer.apply(component.bytes)
} catch (e: Exception) {
throw TransactionDeserialisationException(groupType, index, e)
}
}
}
}

View File

@ -0,0 +1,51 @@
@file:JvmName("LtxConstants")
package net.corda.node.djvm
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.CommandWithParties
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.PrivacySalt
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.contracts.TransactionState
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.node.NetworkParameters
import net.corda.core.transactions.LedgerTransaction
import java.util.function.Function
private const val TX_INPUTS = 0
private const val TX_OUTPUTS = 1
private const val TX_COMMANDS = 2
private const val TX_ATTACHMENTS = 3
private const val TX_ID = 4
private const val TX_NOTARY = 5
private const val TX_TIME_WINDOW = 6
private const val TX_PRIVACY_SALT = 7
private const val TX_NETWORK_PARAMETERS = 8
private const val TX_REFERENCES = 9
class LtxFactory : Function<Array<Any?>, LedgerTransaction> {
@Suppress("unchecked_cast")
override fun apply(txArgs: Array<Any?>): LedgerTransaction {
return LedgerTransaction.createForSandbox(
inputs = (txArgs[TX_INPUTS] as Array<Array<Any?>>).map { it.toStateAndRef() },
outputs = (txArgs[TX_OUTPUTS] as? List<TransactionState<ContractState>>) ?: emptyList(),
commands = (txArgs[TX_COMMANDS] as? List<CommandWithParties<CommandData>>) ?: emptyList(),
attachments = (txArgs[TX_ATTACHMENTS] as? List<Attachment>) ?: emptyList(),
id = txArgs[TX_ID] as SecureHash,
notary = txArgs[TX_NOTARY] as? Party,
timeWindow = txArgs[TX_TIME_WINDOW] as? TimeWindow,
privacySalt = txArgs[TX_PRIVACY_SALT] as PrivacySalt,
networkParameters = txArgs[TX_NETWORK_PARAMETERS] as NetworkParameters,
references = (txArgs[TX_REFERENCES] as Array<Array<Any?>>).map { it.toStateAndRef() }
)
}
private fun Array<*>.toStateAndRef(): StateAndRef<ContractState> {
return StateAndRef(this[0] as TransactionState<*>, this[1] as StateRef)
}
}

View File

@ -0,0 +1,38 @@
package net.corda.contracts.djvm.attachment
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.Contract
import net.corda.core.contracts.ContractState
import net.corda.core.identity.AbstractParty
import net.corda.core.transactions.LedgerTransaction
import java.io.ByteArrayOutputStream
class SandboxAttachmentContract : Contract {
override fun verify(tx: LedgerTransaction) {
val attachments = tx.attachments
require(attachments.isNotEmpty()) { "Attachments are missing for TX=${tx.id}" }
require(attachments.size == 1) { "Did not expect to find ${attachments.size} attachments for TX=${tx.id}" }
val attachment = attachments[0]
require(attachment.size > 0) { "Attachment ${attachment.id} has no contents for TX=${tx.id}" }
val keyCount = attachment.signerKeys.size
require(keyCount == 1) { "Did not expect to find $keyCount signing keys for attachment ${attachment.id}, TX=${tx.id}" }
tx.commandsOfType(ExtractFile::class.java).forEach { extract ->
val fileName = extract.value.fileName
val contents = ByteArrayOutputStream().use {
attachment.extractFile(fileName, it)
it
}.toByteArray()
require(contents.isNotEmpty()) { "File $fileName has no contents for TX=${tx.id}" }
}
}
@Suppress("CanBeParameter", "MemberVisibilityCanBePrivate")
class State(val issuer: AbstractParty) : ContractState {
override val participants: List<AbstractParty> = listOf(issuer)
}
class ExtractFile(val fileName: String) : CommandData
}

View File

@ -0,0 +1,50 @@
package net.corda.contracts.djvm.broken
import net.corda.core.contracts.Contract
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.TypeOnlyCommandData
import net.corda.core.identity.AbstractParty
import net.corda.core.transactions.LedgerTransaction
import java.time.Instant
import java.util.*
class NonDeterministicContract : Contract {
override fun verify(tx: LedgerTransaction) {
when {
tx.commandsOfType(InstantNow::class.java).isNotEmpty() -> verifyInstantNow()
tx.commandsOfType(CurrentTimeMillis::class.java).isNotEmpty() -> verifyCurrentTimeMillis()
tx.commandsOfType(NanoTime::class.java).isNotEmpty() -> verifyNanoTime()
tx.commandsOfType(RandomUUID::class.java).isNotEmpty() -> UUID.randomUUID()
tx.commandsOfType(WithReflection::class.java).isNotEmpty() -> verifyNoReflection()
else -> {}
}
}
private fun verifyInstantNow() {
Instant.now()
}
private fun verifyCurrentTimeMillis() {
System.currentTimeMillis()
}
private fun verifyNanoTime() {
System.nanoTime()
}
private fun verifyNoReflection() {
Date::class.java.getDeclaredConstructor().newInstance()
}
@Suppress("CanBeParameter", "MemberVisibilityCanBePrivate")
class State(val issuer: AbstractParty) : ContractState {
override val participants: List<AbstractParty> = listOf(issuer)
}
class InstantNow : TypeOnlyCommandData()
class CurrentTimeMillis : TypeOnlyCommandData()
class NanoTime : TypeOnlyCommandData()
class RandomUUID : TypeOnlyCommandData()
class WithReflection : TypeOnlyCommandData()
class NoOperation : TypeOnlyCommandData()
}

View File

@ -0,0 +1,39 @@
package net.corda.contracts.djvm.crypto
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.Contract
import net.corda.core.contracts.ContractState
import net.corda.core.crypto.Crypto
import net.corda.core.identity.AbstractParty
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.OpaqueBytes
import java.security.PublicKey
class DeterministicCryptoContract : Contract {
override fun verify(tx: LedgerTransaction) {
val cryptoData = tx.outputsOfType(CryptoState::class.java)
val validators = tx.commandsOfType(Validate::class.java)
val isValid = validators.all { validate ->
with (validate.value) {
cryptoData.all { crypto ->
Crypto.doVerify(schemeCodeName, publicKey, crypto.signature.bytes, crypto.original.bytes)
}
}
}
require(cryptoData.isNotEmpty() && validators.isNotEmpty() && isValid) {
"Failed to validate signatures in command data"
}
}
@Suppress("CanBeParameter", "MemberVisibilityCanBePrivate")
class CryptoState(val owner: AbstractParty, val original: OpaqueBytes, val signature: OpaqueBytes) : ContractState {
override val participants: List<AbstractParty> = listOf(owner)
}
class Validate(
val schemeCodeName: String,
val publicKey: PublicKey
) : CommandData
}

View File

@ -0,0 +1,27 @@
package net.corda.flows.djvm.attachment
import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.djvm.attachment.SandboxAttachmentContract
import net.corda.core.contracts.Command
import net.corda.core.contracts.CommandData
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.transactions.TransactionBuilder
@InitiatingFlow
@StartableByRPC
class SandboxAttachmentFlow(private val command: CommandData) : FlowLogic<SecureHash>() {
@Suspendable
override fun call(): SecureHash {
val notary = serviceHub.networkMapCache.notaryIdentities[0]
val stx = serviceHub.signInitialTransaction(
TransactionBuilder(notary)
.addOutputState(SandboxAttachmentContract.State(ourIdentity))
.addCommand(Command(command, ourIdentity.owningKey))
)
stx.verify(serviceHub, checkSufficientSignatures = false)
return stx.id
}
}

View File

@ -0,0 +1,27 @@
package net.corda.flows.djvm.broken
import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.djvm.broken.NonDeterministicContract
import net.corda.core.contracts.Command
import net.corda.core.contracts.TypeOnlyCommandData
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.transactions.TransactionBuilder
@InitiatingFlow
@StartableByRPC
class NonDeterministicFlow(private val trouble: TypeOnlyCommandData) : FlowLogic<SecureHash>() {
@Suspendable
override fun call(): SecureHash {
val notary = serviceHub.networkMapCache.notaryIdentities[0]
val stx = serviceHub.signInitialTransaction(
TransactionBuilder(notary)
.addOutputState(NonDeterministicContract.State(ourIdentity))
.addCommand(Command(trouble, ourIdentity.owningKey))
)
stx.verify(serviceHub, checkSufficientSignatures = false)
return stx.id
}
}

View File

@ -0,0 +1,32 @@
package net.corda.flows.djvm.crypto
import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.djvm.crypto.DeterministicCryptoContract
import net.corda.core.contracts.Command
import net.corda.core.contracts.CommandData
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes
@InitiatingFlow
@StartableByRPC
class DeterministicCryptoFlow(
private val command: CommandData,
private val original: OpaqueBytes,
private val signature: OpaqueBytes
) : FlowLogic<SecureHash>() {
@Suspendable
override fun call(): SecureHash {
val notary = serviceHub.networkMapCache.notaryIdentities[0]
val stx = serviceHub.signInitialTransaction(
TransactionBuilder(notary)
.addOutputState(DeterministicCryptoContract.CryptoState(ourIdentity, original, signature))
.addCommand(Command(command, ourIdentity.owningKey))
)
stx.verify(serviceHub, checkSufficientSignatures = false)
return stx.id
}
}

View File

@ -0,0 +1,31 @@
package net.corda.node
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.test.fail
class DeterministicSourcesRule : TestRule {
private var deterministicRt: Path? = null
private var deterministicSources: List<Path>? = null
val bootstrap: Path get() = deterministicRt ?: fail("deterministic-rt.path property not set")
val corda: List<Path> get() = deterministicSources ?: fail("deterministic-sources.path property not set")
override fun apply(statement: Statement, description: Description?): Statement {
deterministicRt = System.getProperty("deterministic-rt.path")?.run { Paths.get(this) }
deterministicSources = System.getProperty("deterministic-sources.path")?.split(File.pathSeparator)
?.map { Paths.get(it) }
?.filter { Files.exists(it) }
return object : Statement() {
override fun evaluate() {
statement.evaluate()
}
}
}
}

View File

@ -0,0 +1,73 @@
package net.corda.node.services
import net.corda.contracts.djvm.crypto.DeterministicCryptoContract.Validate
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.Crypto.DEFAULT_SIGNATURE_SCHEME
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.flows.djvm.crypto.DeterministicCryptoFlow
import net.corda.node.DeterministicSourcesRule
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.driver.internal.incrementalPortAllocation
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.internal.CustomCordapp
import net.corda.testing.node.internal.cordappWithPackages
import org.junit.ClassRule
import org.junit.Test
import org.junit.jupiter.api.assertDoesNotThrow
import java.security.KeyPairGenerator
class DeterministicContractCryptoTest {
companion object {
const val MESSAGE = "Very Important Data! Do Not Change!"
val logger = loggerFor<NonDeterministicContractVerifyTest>()
@ClassRule
@JvmField
val djvmSources = DeterministicSourcesRule()
fun parametersFor(djvmSources: DeterministicSourcesRule): DriverParameters {
return DriverParameters(
portAllocation = incrementalPortAllocation(),
startNodesInProcess = false,
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
cordappsForAllNodes = listOf(
cordappWithPackages("net.corda.flows.djvm.crypto"),
CustomCordapp(
packages = setOf("net.corda.contracts.djvm.crypto"),
name = "deterministic-crypto-contract",
signingInfo = CustomCordapp.SigningInfo()
)
),
djvmBootstrapSource = djvmSources.bootstrap,
djvmCordaSource = djvmSources.corda
)
}
}
@Test
fun `test DJVM can verify using crypto`() {
val keyPair = KeyPairGenerator.getInstance(DEFAULT_SIGNATURE_SCHEME.algorithmName).genKeyPair()
val importantData = OpaqueBytes(MESSAGE.toByteArray())
val signature = OpaqueBytes(Crypto.doSign(DEFAULT_SIGNATURE_SCHEME, keyPair.`private`, importantData.bytes))
val validate = Validate(
schemeCodeName = DEFAULT_SIGNATURE_SCHEME.schemeCodeName,
publicKey = keyPair.`public`
)
driver(parametersFor(djvmSources)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val txId = assertDoesNotThrow {
alice.rpc.startFlow(::DeterministicCryptoFlow, validate, importantData, signature)
.returnValue.getOrThrow()
}
logger.info("TX-ID: {}", txId)
}
}
}

View File

@ -0,0 +1,133 @@
package net.corda.node.services
import net.corda.contracts.djvm.broken.NonDeterministicContract.CurrentTimeMillis
import net.corda.contracts.djvm.broken.NonDeterministicContract.InstantNow
import net.corda.contracts.djvm.broken.NonDeterministicContract.NanoTime
import net.corda.contracts.djvm.broken.NonDeterministicContract.NoOperation
import net.corda.contracts.djvm.broken.NonDeterministicContract.RandomUUID
import net.corda.contracts.djvm.broken.NonDeterministicContract.WithReflection
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.flows.djvm.broken.NonDeterministicFlow
import net.corda.node.DeterministicSourcesRule
import net.corda.node.internal.djvm.DeterministicVerificationException
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.driver.internal.incrementalPortAllocation
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.internal.CustomCordapp
import net.corda.testing.node.internal.cordappWithPackages
import org.assertj.core.api.AssertionsForInterfaceTypes.assertThat
import org.junit.ClassRule
import org.junit.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows
class NonDeterministicContractVerifyTest {
companion object {
val logger = loggerFor<NonDeterministicContractVerifyTest>()
@ClassRule
@JvmField
val djvmSources = DeterministicSourcesRule()
fun parametersFor(djvmSources: DeterministicSourcesRule): DriverParameters {
return DriverParameters(
portAllocation = incrementalPortAllocation(),
startNodesInProcess =false,
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
cordappsForAllNodes = listOf(
cordappWithPackages("net.corda.flows.djvm.broken"),
CustomCordapp(
packages = setOf("net.corda.contracts.djvm.broken"),
name = "nondeterministic-contract",
signingInfo = CustomCordapp.SigningInfo()
)
),
djvmBootstrapSource = djvmSources.bootstrap,
djvmCordaSource = djvmSources.corda
)
}
}
@Test
fun `test DJVM rejects contract that uses Instant now`() {
driver(parametersFor(djvmSources)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val ex = assertThrows<DeterministicVerificationException> {
alice.rpc.startFlow(::NonDeterministicFlow, InstantNow())
.returnValue.getOrThrow()
}
assertThat(ex)
.hasMessageStartingWith("NoSuchMethodError: sandbox.java.time.Instant.now()Lsandbox/java/time/Instant;, ")
}
}
@Test
fun `test DJVM rejects contract that uses System currentTimeMillis`() {
driver(parametersFor(djvmSources)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val ex = assertThrows<DeterministicVerificationException> {
alice.rpc.startFlow(::NonDeterministicFlow, CurrentTimeMillis())
.returnValue.getOrThrow()
}
assertThat(ex)
.hasMessageStartingWith("NoSuchMethodError: sandbox.java.lang.System.currentTimeMillis()J, ")
}
}
@Test
fun `test DJVM rejects contract that uses System nanoTime`() {
driver(parametersFor(djvmSources)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val ex = assertThrows<DeterministicVerificationException> {
alice.rpc.startFlow(::NonDeterministicFlow, NanoTime())
.returnValue.getOrThrow()
}
assertThat(ex)
.hasMessageStartingWith("NoSuchMethodError: sandbox.java.lang.System.nanoTime()J, ")
}
}
@Test
fun `test DJVM rejects contract that uses UUID randomUUID`() {
driver(parametersFor(djvmSources)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val ex = assertThrows<DeterministicVerificationException> {
alice.rpc.startFlow(::NonDeterministicFlow, RandomUUID())
.returnValue.getOrThrow()
}
assertThat(ex)
.hasMessageStartingWith("NoSuchMethodError: sandbox.java.util.UUID.randomUUID()Lsandbox/java/util/UUID;, ")
}
}
@Test
fun `test DJVM rejects contract that uses reflection`() {
driver(parametersFor(djvmSources)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val ex = assertThrows<DeterministicVerificationException> {
alice.rpc.startFlow(::NonDeterministicFlow, WithReflection())
.returnValue.getOrThrow()
}
assertThat(ex).hasMessageStartingWith(
"RuleViolationError: Disallowed reference to API; java.lang.Class.getDeclaredConstructor(Class[]), "
)
}
}
@Test
fun `test DJVM can succeed`() {
driver(parametersFor(djvmSources)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val txId = assertDoesNotThrow {
alice.rpc.startFlow(::NonDeterministicFlow, NoOperation())
.returnValue.getOrThrow()
}
logger.info("TX-ID: {}", txId)
}
}
}

View File

@ -0,0 +1,81 @@
package net.corda.node.services
import net.corda.contracts.djvm.attachment.SandboxAttachmentContract
import net.corda.contracts.djvm.attachment.SandboxAttachmentContract.ExtractFile
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.djvm.code.asResourcePath
import net.corda.flows.djvm.attachment.SandboxAttachmentFlow
import net.corda.node.DeterministicSourcesRule
import net.corda.node.internal.djvm.DeterministicVerificationException
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.driver.internal.incrementalPortAllocation
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.internal.CustomCordapp
import net.corda.testing.node.internal.cordappWithPackages
import org.assertj.core.api.Assertions.assertThat
import org.junit.ClassRule
import org.junit.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows
class SandboxAttachmentsTest {
companion object {
val logger = loggerFor<SandboxAttachmentsTest>()
@ClassRule
@JvmField
val djvmSources = DeterministicSourcesRule()
fun parametersFor(djvmSources: DeterministicSourcesRule): DriverParameters {
return DriverParameters(
portAllocation = incrementalPortAllocation(),
startNodesInProcess = false,
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
cordappsForAllNodes = listOf(
cordappWithPackages("net.corda.flows.djvm.attachment"),
CustomCordapp(
packages = setOf("net.corda.contracts.djvm.attachment"),
name = "sandbox-attachment-contract",
signingInfo = CustomCordapp.SigningInfo()
)
),
djvmBootstrapSource = djvmSources.bootstrap,
djvmCordaSource = djvmSources.corda
)
}
}
@Test
fun `test attachment accessible within sandbox`() {
val extractFile = ExtractFile(SandboxAttachmentContract::class.java.name.asResourcePath + ".class")
driver(parametersFor(djvmSources)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val txId = assertDoesNotThrow {
alice.rpc.startFlow(::SandboxAttachmentFlow, extractFile)
.returnValue.getOrThrow()
}
logger.info("TX-ID: {}", txId)
}
}
@Test
fun `test attachment file not found within sandbox`() {
val extractFile = ExtractFile("does/not/Exist.class")
driver(parametersFor(djvmSources)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val ex = assertThrows<DeterministicVerificationException> {
alice.rpc.startFlow(::SandboxAttachmentFlow, extractFile)
.returnValue.getOrThrow()
}
assertThat(ex)
.hasMessageStartingWith("sandbox.net.corda.core.contracts.TransactionVerificationException\$ContractRejection -> ")
.hasMessageContaining(" Contract verification failed: does/not/Exist.class, " )
.hasMessageContaining(" contract: sandbox.net.corda.contracts.djvm.attachment.SandboxAttachmentContract, ")
}
}
}

View File

@ -12,38 +12,84 @@ import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.internal.AliasPrivateKey
import net.corda.core.crypto.newSecureRandom
import net.corda.core.flows.*
import net.corda.core.flows.ContractUpgradeFlow
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowLogicRefFactory
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.NotaryChangeFlow
import net.corda.core.flows.NotaryFlow
import net.corda.core.flows.StartableByService
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.*
import net.corda.core.internal.AttachmentTrustCalculator
import net.corda.core.internal.FlowStateMachine
import net.corda.core.internal.NODE_INFO_DIRECTORY
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.NetworkParametersStorage
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.concurrent.doneFuture
import net.corda.core.internal.concurrent.map
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.div
import net.corda.core.internal.messaging.InternalCordaRPCOps
import net.corda.core.internal.notary.NotaryService
import net.corda.core.messaging.*
import net.corda.core.node.*
import net.corda.core.node.services.*
import net.corda.core.internal.rootMessage
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.ClientRpcSslOptions
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.FlowHandle
import net.corda.core.messaging.FlowHandleImpl
import net.corda.core.messaging.FlowProgressHandle
import net.corda.core.messaging.FlowProgressHandleImpl
import net.corda.core.messaging.RPCOps
import net.corda.core.node.AppServiceHub
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.ContractUpgradeService
import net.corda.core.node.services.CordaService
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.KeyManagementService
import net.corda.core.node.services.TransactionVerifierService
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.LedgerTransaction
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.EmptyApi
import net.corda.djvm.source.UserSource
import net.corda.node.CordaClock
import net.corda.node.VersionInfo
import net.corda.node.internal.classloading.requireAnnotation
import net.corda.node.internal.cordapp.*
import net.corda.node.internal.cordapp.CordappConfigFileProvider
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.node.internal.cordapp.CordappProviderInternal
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.node.internal.cordapp.VirtualCordapp
import net.corda.node.internal.rpc.proxies.AuthenticatedRpcOpsProxy
import net.corda.node.internal.rpc.proxies.ThreadContextAdjustingRpcOpsProxy
import net.corda.node.services.ContractUpgradeHandler
import net.corda.node.services.FinalityHandler
import net.corda.node.services.NotaryChangeHandler
import net.corda.node.services.api.*
import net.corda.node.services.api.AuditService
import net.corda.node.services.api.DummyAuditService
import net.corda.node.services.api.FlowStarter
import net.corda.node.services.api.MonitoringService
import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.node.services.api.NodePropertiesStore
import net.corda.node.services.api.SchemaService
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.api.VaultServiceInternal
import net.corda.node.services.api.WritableTransactionStorage
import net.corda.node.services.attachments.NodeAttachmentTrustCalculator
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.configureWithDevSSLCertificate
@ -61,15 +107,36 @@ import net.corda.node.services.network.NetworkMapClient
import net.corda.node.services.network.NetworkMapUpdater
import net.corda.node.services.network.NodeInfoWatcher
import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.persistence.*
import net.corda.node.services.persistence.AbstractPartyDescriptor
import net.corda.node.services.persistence.AbstractPartyToX500NameAsStringConverter
import net.corda.node.services.persistence.AttachmentStorageInternal
import net.corda.node.services.persistence.DBCheckpointStorage
import net.corda.node.services.persistence.DBTransactionMappingStorage
import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.node.services.persistence.NodePropertiesPersistentStore
import net.corda.node.services.persistence.PublicKeyToOwningIdentityCacheImpl
import net.corda.node.services.persistence.PublicKeyToTextConverter
import net.corda.node.services.rpc.CheckpointDumper
import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.statemachine.*
import net.corda.node.services.statemachine.ExternalEvent
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
import net.corda.node.services.statemachine.FlowMonitor
import net.corda.node.services.statemachine.SingleThreadedStateMachineManager
import net.corda.node.services.statemachine.StaffedFlowHospital
import net.corda.node.services.statemachine.StateMachineManager
import net.corda.node.services.statemachine.StateMachineManagerInternal
import net.corda.node.services.transactions.BasicVerifierFactoryService
import net.corda.node.services.transactions.DeterministicVerifierFactoryService
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.node.services.transactions.VerifierFactoryService
import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
import net.corda.node.services.vault.NodeVaultService
import net.corda.node.utilities.*
import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.BindableNamedCacheFactory
import net.corda.node.utilities.NamedThreadFactory
import net.corda.node.utilities.NotaryLoader
import net.corda.nodeapi.internal.NodeInfoAndSigned
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.config.CertificateStore
@ -85,7 +152,12 @@ import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_ALIAS_PREFI
import net.corda.nodeapi.internal.cryptoservice.CryptoServiceFactory
import net.corda.nodeapi.internal.cryptoservice.SupportedCryptoServices
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
import net.corda.nodeapi.internal.persistence.*
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.DatabaseIncompatibleException
import net.corda.nodeapi.internal.persistence.OutstandingDatabaseChangesException
import net.corda.nodeapi.internal.persistence.SchemaMigration
import net.corda.tools.shell.InteractiveShell
import org.apache.activemq.artemis.utils.ReusableLatch
import org.jolokia.jvmagent.JolokiaServer
@ -103,13 +175,15 @@ import java.sql.Connection
import java.time.Clock
import java.time.Duration
import java.time.format.DateTimeParseException
import java.util.*
import java.util.Objects
import java.util.Properties
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit.MINUTES
import java.util.concurrent.TimeUnit.SECONDS
import java.util.function.Consumer
import javax.persistence.EntityManager
import kotlin.collections.ArrayList
/**
* A base node implementation that can be customised either for production (with real implementations that do real
@ -125,7 +199,9 @@ 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(),
djvmBootstrapSource: ApiSource = EmptyApi,
djvmCordaSource: UserSource? = null) : SingletonSerializeAsToken() {
protected abstract val log: Logger
@Suppress("LeakingThis")
@ -214,6 +290,18 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
).closeOnStop()
@Suppress("LeakingThis")
val transactionVerifierService = InMemoryTransactionVerifierService(transactionVerifierWorkerCount).tokenize()
val verifierFactoryService: VerifierFactoryService = if (djvmCordaSource != null) {
DeterministicVerifierFactoryService(djvmBootstrapSource, djvmCordaSource).apply {
log.info("DJVM sandbox enabled for deterministic contract verification.")
if (!configuration.devMode) {
log.info("Generating Corda classes for DJVM sandbox.")
generateSandbox()
}
tokenize()
}
} else {
BasicVerifierFactoryService()
}
val contractUpgradeService = ContractUpgradeServiceImpl(cacheFactory).tokenize()
val auditService = DummyAuditService().tokenize()
@Suppress("LeakingThis")
@ -326,7 +414,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
open fun start(): S {
check(started == null) { "Node has already been started" }
if (configuration.devMode) {
if (configuration.devMode && System.getProperty("co.paralleluniverse.fibers.verifyInstrumentation") == null) {
System.setProperty("co.paralleluniverse.fibers.verifyInstrumentation", "true")
}
log.info("Node starting up ...")
@ -1069,6 +1157,11 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
override fun registerUnloadHandler(runOnStop: () -> Unit) {
this@AbstractNode.runOnStop += runOnStop
}
override fun specialise(ltx: LedgerTransaction): LedgerTransaction {
val ledgerTransaction = servicesForResolution.specialise(ltx)
return verifierFactoryService.apply(ledgerTransaction)
}
}
}

View File

@ -4,6 +4,7 @@ import com.codahale.metrics.MetricFilter
import com.codahale.metrics.MetricRegistry
import com.codahale.metrics.jmx.JmxReporter
import com.github.benmanes.caffeine.cache.Caffeine
import com.jcabi.manifests.Manifests
import com.palominolabs.metrics.newrelic.AllEnabledMetricAttributeFilter
import com.palominolabs.metrics.newrelic.NewRelicReporter
import io.netty.util.NettyRuntime
@ -19,6 +20,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 +31,11 @@ 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.djvm.source.UserPathSource
import net.corda.djvm.source.UserSource
import net.corda.node.CordaClock
import net.corda.node.SimpleClock
import net.corda.node.VersionInfo
@ -44,7 +51,12 @@ import net.corda.node.services.Permissions
import net.corda.node.services.api.FlowStarter
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.api.StartedNodeServices
import net.corda.node.services.config.*
import net.corda.node.services.config.JmxReporterType
import net.corda.node.services.config.MB
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.SecurityConfiguration
import net.corda.node.services.config.shouldInitCrashShell
import net.corda.node.services.config.shouldStartLocalShell
import net.corda.node.services.messaging.ArtemisMessagingServer
import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.messaging.P2PMessagingClient
@ -52,7 +64,12 @@ import net.corda.node.services.rpc.ArtemisRpcBroker
import net.corda.node.services.rpc.InternalRPCMessagingClient
import net.corda.node.services.rpc.RPCServerConfiguration
import net.corda.node.services.statemachine.StateMachineManager
import net.corda.node.utilities.*
import net.corda.node.utilities.AddressUtils
import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.BindableNamedCacheFactory
import net.corda.node.utilities.DefaultNamedCacheFactory
import net.corda.node.utilities.DemoClock
import net.corda.node.utilities.errorAndTerminate
import net.corda.nodeapi.internal.ArtemisMessagingClient
import net.corda.nodeapi.internal.ArtemisMessagingComponent
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_SHELL_USER
@ -62,7 +79,11 @@ import net.corda.nodeapi.internal.bridging.BridgeControlListener
import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
import net.corda.serialization.internal.*
import net.corda.serialization.internal.AMQP_P2P_CONTEXT
import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
import net.corda.serialization.internal.AMQP_RPC_SERVER_CONTEXT
import net.corda.serialization.internal.AMQP_STORAGE_CONTEXT
import net.corda.serialization.internal.SerializationFactoryImpl
import net.corda.serialization.internal.amqp.SerializationFactoryCacheKey
import net.corda.serialization.internal.amqp.SerializerFactory
import org.apache.commons.lang3.SystemUtils
@ -75,6 +96,7 @@ import java.lang.Long.max
import java.lang.Long.min
import java.net.BindException
import java.net.InetAddress
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.time.Clock
@ -99,7 +121,9 @@ open class Node(configuration: NodeConfiguration,
versionInfo: VersionInfo,
private val initialiseSerialization: Boolean = true,
flowManager: FlowManager = NodeFlowManager(configuration.flowOverrides),
cacheFactoryPrototype: BindableNamedCacheFactory = DefaultNamedCacheFactory()
cacheFactoryPrototype: BindableNamedCacheFactory = DefaultNamedCacheFactory(),
djvmBootstrapSource: ApiSource = createBootstrapSource(configuration),
djvmCordaSource: UserSource? = createCordaSource(configuration)
) : AbstractNode<NodeInfo>(
configuration,
createClock(configuration),
@ -107,13 +131,19 @@ 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),
djvmBootstrapSource = djvmBootstrapSource,
djvmCordaSource = djvmCordaSource
) {
override fun createStartedNode(nodeInfo: NodeInfo, rpcOps: CordaRPCOps, notaryService: NotaryService?): NodeInfo =
nodeInfo
companion object {
private const val CORDA_DETERMINISTIC_RUNTIME_ATTR = "Corda-Deterministic-Runtime"
private const val CORDA_DETERMINISTIC_CLASSPATH_ATTR = "Corda-Deterministic-Classpath"
private const val CORDA_DJVM = "net.corda.djvm"
private val staticLog = contextLogger()
var renderBasicInfoToConsole = true
@ -172,6 +202,74 @@ open class Node(configuration: NodeConfiguration,
false
}
}
private fun manifestValue(attrName: String): String? = if (Manifests.exists(attrName)) Manifests.read(attrName) else null
private fun createManifestCordaSource(config: NodeConfiguration): UserSource? {
val classpathSource = config.baseDirectory.resolve("djvm")
val djvmClasspath = manifestValue(CORDA_DETERMINISTIC_CLASSPATH_ATTR)
return if (djvmClasspath == null) {
staticLog.warn("{} missing from MANIFEST.MF - deterministic contract verification now impossible!",
CORDA_DETERMINISTIC_CLASSPATH_ATTR)
null
} else if (!Files.isDirectory(classpathSource)) {
staticLog.warn("{} directory does not exist - deterministic contract verification now impossible!",
classpathSource.toAbsolutePath())
null
} else {
val files = djvmClasspath.split("\\s++".toRegex(), 0).map { classpathSource.resolve(it) }
.filter { Files.isRegularFile(it) || Files.isSymbolicLink(it) }
staticLog.info("Corda Deterministic Libraries: {}", files.map(Path::getFileName).joinToString())
val jars = files.map { it.toUri().toURL() }.toTypedArray()
UserPathSource(jars)
}
}
private fun createManifestBootstrapSource(config: NodeConfiguration): ApiSource {
val deterministicRt = manifestValue(CORDA_DETERMINISTIC_RUNTIME_ATTR)
if (deterministicRt == null) {
staticLog.warn("{} missing from MANIFEST.MF - will use host JVM for deterministic runtime.",
CORDA_DETERMINISTIC_RUNTIME_ATTR)
return EmptyApi
}
val bootstrapSource = config.baseDirectory.resolve("djvm").resolve(deterministicRt)
return if (bootstrapSource.isRegularFile()) {
staticLog.info("Deterministic Runtime: {}", bootstrapSource.fileName)
BootstrapClassLoader(bootstrapSource)
} else {
staticLog.warn("NO DETERMINISTIC RUNTIME FOUND - will use host JVM instead.")
EmptyApi
}
}
private fun createBootstrapSource(config: NodeConfiguration): ApiSource {
val djvm = config.devModeOptions?.djvm
return if (config.devMode && djvm != null) {
djvm.bootstrapSource?.let { BootstrapClassLoader(Paths.get(it)) } ?: EmptyApi
} else if (java.lang.Boolean.getBoolean(CORDA_DJVM)) {
createManifestBootstrapSource(config)
} else {
EmptyApi
}
}
private fun createCordaSource(config: NodeConfiguration): UserSource? {
val djvm = config.devModeOptions?.djvm
return if (config.devMode && djvm != null) {
if (djvm.cordaSource.isEmpty()) {
null
} else {
UserPathSource(djvm.cordaSource.map { Paths.get(it) })
}
} else if (java.lang.Boolean.getBoolean(CORDA_DJVM)) {
createManifestCordaSource(config)
} else {
null
}
}
}
override val log: Logger get() = staticLog

View File

@ -35,7 +35,7 @@ data class ServicesForResolutionImpl(
return stateRefs.groupBy { it.txhash }.flatMap {
val stx = validatedTransactions.getTransaction(it.key) ?: throw TransactionResolutionException(it.key)
val baseTx = stx.resolveBaseTransaction(this)
it.value.map { StateAndRef(baseTx.outputs[it.index], it) }
it.value.map { ref -> StateAndRef(baseTx.outputs[ref.index], ref) }
}.toSet()
}

View File

@ -0,0 +1,32 @@
package net.corda.node.internal.djvm
import net.corda.core.contracts.Attachment
import net.corda.core.serialization.serialize
import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.node.djvm.AttachmentBuilder
import java.util.function.Function
class AttachmentFactory(
private val classLoader: SandboxClassLoader,
private val taskFactory: Function<in Any, out Function<in Any?, out Any?>>,
private val sandboxBasicInput: Function<in Any?, out Any?>,
private val serializer: Serializer
) {
private val sandboxOpenAttachment: Function<in Attachment, out Any?> = classLoader.createForImport(
Function(Attachment::open).andThen(sandboxBasicInput)
)
fun toSandbox(attachments: List<Attachment>): Any? {
val builder = classLoader.createTaskFor(taskFactory, AttachmentBuilder::class.java)
for (attachment in attachments) {
builder.apply(arrayOf(
serializer.deserialize(attachment.signerKeys.serialize()),
sandboxBasicInput.apply(attachment.size),
serializer.deserialize(attachment.id.serialize()),
attachment,
sandboxOpenAttachment
))
}
return builder.apply(null)
}
}

View File

@ -0,0 +1,19 @@
package net.corda.node.internal.djvm
import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.node.djvm.CommandBuilder
import java.util.function.Function
class CommandFactory(
private val classLoader: SandboxClassLoader,
private val taskFactory: Function<in Any, out Function<in Any?, out Any?>>
) {
fun toSandbox(signers: Any?, commands: Any?, partialMerkleLeafIndices: IntArray?): Any? {
val builder = classLoader.createTaskFor(taskFactory, CommandBuilder::class.java)
return builder.apply(arrayOf(
signers,
commands,
partialMerkleLeafIndices
))
}
}

View File

@ -0,0 +1,43 @@
@file:JvmName("ComponentUtils")
package net.corda.node.internal.djvm
import net.corda.core.contracts.ComponentGroupEnum
import net.corda.core.crypto.componentHash
import net.corda.core.transactions.ComponentGroup
import net.corda.core.transactions.FilteredComponentGroup
import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.node.djvm.ComponentBuilder
import java.util.function.Function
class ComponentFactory(
private val classLoader: SandboxClassLoader,
private val taskFactory: Function<in Any, out Function<in Any?, out Any?>>,
private val sandboxBasicInput: Function<in Any?, out Any?>,
private val serializer: Serializer,
private val componentGroups: List<ComponentGroup>
) {
fun toSandbox(
groupType: ComponentGroupEnum,
clazz: Class<*>
): Any? {
val components = (componentGroups.firstOrNull(groupType::isSameType) ?: return null).components
val componentBytes = Array(components.size) { idx -> components[idx].bytes }
return classLoader.createTaskFor(taskFactory, ComponentBuilder::class.java).apply(arrayOf(
classLoader.createForImport(serializer.deserializerFor(clazz)),
sandboxBasicInput.apply(groupType),
componentBytes
))
}
fun calculateLeafIndicesFor(groupType: ComponentGroupEnum): IntArray? {
val componentGroup = componentGroups.firstOrNull(groupType::isSameType) as? FilteredComponentGroup ?: return null
val componentHashes = componentGroup.components.mapIndexed { index, component ->
componentHash(componentGroup.nonces[index], component)
}
return componentHashes.map { componentGroup.partialMerkleTree.leafIndex(it) }.toIntArray()
}
}
private fun ComponentGroupEnum.isSameType(group: ComponentGroup): Boolean {
return group.groupIndex == ordinal
}

View File

@ -0,0 +1,104 @@
package net.corda.node.internal.djvm
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ComponentGroupEnum.COMMANDS_GROUP
import net.corda.core.contracts.ComponentGroupEnum.OUTPUTS_GROUP
import net.corda.core.contracts.ComponentGroupEnum.SIGNERS_GROUP
import net.corda.core.contracts.TransactionState
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.serialization.serialize
import net.corda.core.transactions.LedgerTransaction
import net.corda.djvm.SandboxConfiguration
import net.corda.djvm.execution.ExecutionSummary
import net.corda.djvm.execution.IsolatedTask
import net.corda.djvm.execution.SandboxException
import net.corda.djvm.messages.Message
import net.corda.djvm.source.ClassSource
import net.corda.node.djvm.LtxFactory
class DeterministicVerifier(
ltx: LedgerTransaction,
transactionClassLoader: ClassLoader,
private val sandboxConfiguration: SandboxConfiguration
) : Verifier(ltx, transactionClassLoader) {
override fun verifyContracts() {
val result = IsolatedTask(ltx.id.toString(), sandboxConfiguration).run {
val taskFactory = classLoader.createRawTaskFactory()
val sandboxBasicInput = classLoader.createBasicInput()
/**
* Deserialise the [LedgerTransaction] again into something
* that we can execute inside the DJVM's sandbox.
*/
val sandboxTx = ltx.transform { componentGroups, serializedInputs, serializedReferences ->
val serializer = Serializer(classLoader)
val componentFactory = ComponentFactory(
classLoader,
taskFactory,
sandboxBasicInput,
serializer,
componentGroups
)
val attachmentFactory = AttachmentFactory(
classLoader,
taskFactory,
sandboxBasicInput,
serializer
)
val idData = ltx.id.serialize()
val notaryData = ltx.notary?.serialize()
val timeWindowData = ltx.timeWindow?.serialize()
val privacySaltData = ltx.privacySalt.serialize()
val networkingParametersData = ltx.networkParameters?.serialize()
val createSandboxTx = classLoader.createTaskFor(taskFactory, LtxFactory::class.java)
createSandboxTx.apply(arrayOf(
serializer.deserialize(serializedInputs),
componentFactory.toSandbox(OUTPUTS_GROUP, TransactionState::class.java),
CommandFactory(classLoader, taskFactory).toSandbox(
componentFactory.toSandbox(SIGNERS_GROUP, List::class.java),
componentFactory.toSandbox(COMMANDS_GROUP, CommandData::class.java),
componentFactory.calculateLeafIndicesFor(COMMANDS_GROUP)
),
attachmentFactory.toSandbox(ltx.attachments),
serializer.deserialize(idData),
serializer.deserialize(notaryData),
serializer.deserialize(timeWindowData),
serializer.deserialize(privacySaltData),
serializer.deserialize(networkingParametersData),
serializer.deserialize(serializedReferences)
))
}
val verifier = classLoader.createTaskFor(taskFactory, ContractVerifier::class.java)
// Now execute the contract verifier task within the sandbox...
verifier.apply(sandboxTx)
}
with (result.costs) {
logger.info("Verify {} complete: allocations={}, invocations={}, jumps={}, throws={}",
ltx.id, allocations, invocations, jumps, throws)
}
result.exception?.run {
val sandboxEx = SandboxException(
Message.getMessageFromException(this),
result.identifier,
ClassSource.fromClassName(ContractVerifier::class.java.name),
ExecutionSummary(result.costs),
this
)
logger.error("Error validating transaction ${ltx.id}.", sandboxEx)
throw DeterministicVerificationException(ltx.id, sandboxEx.message ?: "", sandboxEx)
}
}
}
class DeterministicVerificationException(txId: SecureHash, message: String, cause: Throwable)
: TransactionVerificationException(txId, message, cause)

View File

@ -0,0 +1,58 @@
package net.corda.node.internal.djvm
import net.corda.core.internal.SerializedStateAndRef
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationFactory
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.serialize
import net.corda.core.utilities.ByteSequence
import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.node.djvm.ComponentBuilder
import net.corda.serialization.djvm.createSandboxSerializationEnv
import java.util.function.Function
class Serializer(private val classLoader: SandboxClassLoader) {
private val factory: SerializationFactory
private val context: SerializationContext
init {
val env = createSandboxSerializationEnv(classLoader)
factory = env.serializationFactory
context = env.p2pContext
}
/**
* Convert a list of [SerializedStateAndRef] objects into arrays
* of deserialized sandbox objects. We will pass this array into
* [net.corda.node.djvm.LtxFactory] to be transformed finally to
* a list of [net.corda.core.contracts.StateAndRef] objects,
*/
fun deserialize(stateRefs: List<SerializedStateAndRef>): Array<Array<Any?>> {
return stateRefs.map {
arrayOf(deserialize(it.serializedState), deserialize(it.ref.serialize()))
}.toTypedArray()
}
/**
* Generate a [Function] that deserializes a [ByteArray] into an instance
* of the given sandbox class. We import this [Function] into the sandbox
* so that [ComponentBuilder] can deserialize objects lazily.
*/
fun deserializerFor(clazz: Class<*>): Function<ByteArray?, out Any?> {
val sandboxClass = classLoader.toSandboxClass(clazz)
return Function { bytes ->
bytes?.run {
factory.deserialize(ByteSequence.of(this), sandboxClass, context)
}
}
}
fun deserializeTo(clazz: Class<*>, bytes: ByteSequence): Any {
val sandboxClass = classLoader.toSandboxClass(clazz)
return factory.deserialize(bytes, sandboxClass, context)
}
inline fun <reified T : Any> deserialize(bytes: SerializedBytes<T>?): Any? {
return deserializeTo(T::class.java, bytes ?: return null)
}
}

View File

@ -54,8 +54,13 @@ object ConfigHelper {
.withFallback(defaultConfig)
.resolve()
val entrySet = finalConfig.entrySet().filter { entry -> entry.key.contains("\"") }
for ((key) in entrySet) {
val entrySet = finalConfig.entrySet().filter { entry ->
// System properties can reasonably be expected to contain '.'. However, only
// entries that match this pattern are allowed to contain a '"' character:
// systemProperties."text-without-any-quotes-in"
with(entry.key) { contains("\"") && !matches("^systemProperties\\.\"[^\"]++\"\$".toRegex()) }
}
for (key in entrySet) {
log.error("Config files should not contain \" in property names. Please fix: $key")
}
@ -63,8 +68,10 @@ object ConfigHelper {
}
private fun Config.cordaEntriesOnly(): Config {
return ConfigFactory.parseMap(toProperties().filterKeys { (it as String).startsWith(CORDA_PROPERTY_PREFIX) }.mapKeys { (it.key as String).removePrefix(CORDA_PROPERTY_PREFIX) })
return ConfigFactory.parseMap(toProperties()
.filterKeys { (it as String).startsWith(CORDA_PROPERTY_PREFIX) }
.mapKeys { (it.key as String).removePrefix(CORDA_PROPERTY_PREFIX) }
)
}
}

View File

@ -116,15 +116,22 @@ enum class JmxReporterType {
JOLOKIA, NEW_RELIC
}
data class DevModeOptions(val disableCheckpointChecker: Boolean = Defaults.disableCheckpointChecker, val allowCompatibilityZone: Boolean = Defaults.disableCheckpointChecker) {
data class DevModeOptions(
val disableCheckpointChecker: Boolean = Defaults.disableCheckpointChecker,
val allowCompatibilityZone: Boolean = Defaults.allowCompatibilityZone,
val djvm: DJVMOptions? = null
) {
internal object Defaults {
val disableCheckpointChecker = false
val allowCompatibilityZone = false
}
}
data class DJVMOptions(
val bootstrapSource: String?,
val cordaSource: List<String>
)
fun NodeConfiguration.shouldCheckCheckpoints(): Boolean {
return this.devMode && this.devModeOptions?.disableCheckpointChecker != true
}

View File

@ -17,6 +17,7 @@ import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.node.services.config.AuthDataSourceType
import net.corda.node.services.config.CertChainPolicyConfig
import net.corda.node.services.config.CertChainPolicyType
import net.corda.node.services.config.DJVMOptions
import net.corda.node.services.config.DevModeOptions
import net.corda.node.services.config.FlowOverride
import net.corda.node.services.config.FlowOverrideConfig
@ -127,9 +128,19 @@ internal object SecurityConfigurationSpec : Configuration.Specification<Security
internal object DevModeOptionsSpec : Configuration.Specification<DevModeOptions>("DevModeOptions") {
private val disableCheckpointChecker by boolean().optional().withDefaultValue(DevModeOptions.Defaults.disableCheckpointChecker)
private val allowCompatibilityZone by boolean().optional().withDefaultValue(DevModeOptions.Defaults.allowCompatibilityZone)
private val djvm by nested(DJVMOptionsSpec).optional()
private object DJVMOptionsSpec : Configuration.Specification<DJVMOptions>("DJVMOptions") {
private val bootstrapSource by string().optional()
private val cordaSource by string().list()
override fun parseValid(configuration: Config): Valid<DJVMOptions> {
return valid(DJVMOptions(configuration[bootstrapSource], configuration[cordaSource]))
}
}
override fun parseValid(configuration: Config): Valid<DevModeOptions> {
return valid(DevModeOptions(configuration[disableCheckpointChecker], configuration[allowCompatibilityZone]))
return valid(DevModeOptions(configuration[disableCheckpointChecker], configuration[allowCompatibilityZone], configuration[djvm]))
}
}

View File

@ -0,0 +1,92 @@
package net.corda.node.services.transactions
import net.corda.core.internal.BasicVerifier
import net.corda.core.internal.Verifier
import net.corda.core.serialization.ConstructorForDeserialization
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.LedgerTransaction
import net.corda.djvm.SandboxConfiguration
import net.corda.djvm.analysis.AnalysisConfiguration
import net.corda.djvm.analysis.Whitelist
import net.corda.djvm.execution.ExecutionProfile
import net.corda.djvm.source.ApiSource
import net.corda.djvm.source.UserPathSource
import net.corda.djvm.source.UserSource
import net.corda.node.internal.djvm.DeterministicVerifier
import java.net.URL
import java.net.URLClassLoader
import java.util.function.UnaryOperator
interface VerifierFactoryService : UnaryOperator<LedgerTransaction>, AutoCloseable
class DeterministicVerifierFactoryService(
private val bootstrapSource: ApiSource,
private val cordaSource: UserSource
) : SingletonSerializeAsToken(), VerifierFactoryService {
private val baseSandboxConfiguration: SandboxConfiguration
init {
val baseAnalysisConfiguration = AnalysisConfiguration.createRoot(
userSource = cordaSource,
whitelist = Whitelist.MINIMAL,
visibleAnnotations = setOf(
CordaSerializable::class.java,
ConstructorForDeserialization::class.java,
DeprecatedConstructorForDeserialization::class.java
),
bootstrapSource = bootstrapSource
)
baseSandboxConfiguration = SandboxConfiguration.createFor(
analysisConfiguration = baseAnalysisConfiguration,
profile = NODE_PROFILE,
enableTracing = true
)
}
/**
* Generate sandbox classes for every Corda jar with META-INF/DJVM-preload.
*/
fun generateSandbox(): DeterministicVerifierFactoryService {
baseSandboxConfiguration.preload()
return this
}
override fun apply(ledgerTransaction: LedgerTransaction): LedgerTransaction {
// Specialise the LedgerTransaction here so that
// contracts are verified inside the DJVM!
return ledgerTransaction.specialise(::specialise)
}
private fun specialise(ltx: LedgerTransaction, classLoader: ClassLoader): Verifier {
return (classLoader as? URLClassLoader)?.run {
DeterministicVerifier(ltx, classLoader, createSandbox(classLoader.urLs))
} ?: BasicVerifier(ltx, classLoader)
}
private fun createSandbox(userSource: Array<URL>): SandboxConfiguration {
return baseSandboxConfiguration.createChild(UserPathSource(userSource))
}
override fun close() {
bootstrapSource.use {
cordaSource.close()
}
}
private companion object {
private val NODE_PROFILE = ExecutionProfile(
allocationCostThreshold = 1024 * 1024 * 1024,
invocationCostThreshold = 100_000_000,
jumpCostThreshold = 500_000_000,
throwCostThreshold = 1_000_000
)
}
}
class BasicVerifierFactoryService : VerifierFactoryService {
override fun apply(ledgerTransaction: LedgerTransaction)= ledgerTransaction
override fun close() {}
}

View File

@ -54,6 +54,7 @@ task patchSerialization(type: Zip, dependsOn: serializationJarTask) {
archiveExtension = 'jar'
from(compileKotlin)
from(processResources)
from(zipTree(originalJar)) {
exclude 'net/corda/serialization/internal/AttachmentsClassLoaderBuilder*'
exclude 'net/corda/serialization/internal/ByteBufferStreams*'

View File

@ -0,0 +1,69 @@
plugins {
id 'org.jetbrains.kotlin.jvm'
id 'net.corda.plugins.publish-utils'
id 'com.jfrog.artifactory'
id 'java-library'
id 'idea'
}
description 'Serialization support for the DJVM'
configurations {
sandboxTesting
jdkRt.resolutionStrategy {
// Always check the repository for a newer SNAPSHOT.
cacheChangingModulesFor 0, 'seconds'
}
}
dependencies {
api project(':core')
api project(':serialization')
api "net.corda.djvm:corda-djvm:$djvm_version"
implementation(project(':serialization-djvm:deserializers')) {
transitive = false
}
testImplementation "org.junit.jupiter:junit-jupiter-api:$junit_jupiter_version"
testImplementation "org.junit.jupiter:junit-jupiter-params:$junit_jupiter_version"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit_jupiter_version"
// Test utilities
testImplementation "org.assertj:assertj-core:$assertj_version"
testRuntimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
jdkRt "net.corda:deterministic-rt:latest.integration"
// The DJVM will need this classpath to run the unit tests.
sandboxTesting files(sourceSets.getByName("test").output)
sandboxTesting project(':serialization-djvm:deserializers')
sandboxTesting project(path: ':serialization-deterministic', configuration: 'deterministicArtifacts')
sandboxTesting "org.slf4j:slf4j-nop:$slf4j_version"
}
jar {
archiveBaseName = 'corda-serialization-djvm'
manifest {
attributes('Automatic-Module-Name': 'net.corda.serialization.djvm')
attributes('Sealed': true)
}
}
tasks.withType(Test) {
useJUnitPlatform()
systemProperty 'deterministic-rt.path', configurations.jdkRt.asPath
systemProperty 'sandbox-libraries.path', configurations.sandboxTesting.asPath
// Configure the host timezone to match the DJVM's.
systemProperty 'user.timezone', 'UTC'
}
publish {
name jar.archiveBaseName.get()
}
idea {
module {
downloadJavadoc = true
downloadSources = true
}
}

View File

@ -0,0 +1,34 @@
plugins {
id 'org.jetbrains.kotlin.jvm'
id 'net.corda.plugins.publish-utils'
id 'com.jfrog.artifactory'
id 'java-library'
id 'idea'
}
apply from: '../../deterministic.gradle'
description 'Deserializers for the DJVM'
dependencies {
api project(path: ':core-deterministic', configuration: 'deterministicArtifacts')
api project(path: ':serialization-deterministic', configuration: 'deterministicArtifacts')
}
jar {
archiveBaseName = 'corda-deserializers-djvm'
manifest {
attributes('Automatic-Module-Name': 'net.corda.serialization.djvm.deserializers')
attributes('Sealed': true)
}
}
publish {
name jar.archiveBaseName.get()
}
idea {
module {
downloadJavadoc = true
downloadSources = true
}
}

View File

@ -0,0 +1,11 @@
package net.corda.serialization.djvm.deserializers
import net.corda.serialization.internal.amqp.custom.BitSetSerializer.BitSetProxy
import java.util.BitSet
import java.util.function.Function
class BitSetDeserializer : Function<BitSetProxy, BitSet> {
override fun apply(proxy: BitSetProxy): BitSet {
return BitSet.valueOf(proxy.bytes)
}
}

View File

@ -0,0 +1,13 @@
package net.corda.serialization.djvm.deserializers
import net.corda.serialization.internal.amqp.custom.CertPathSerializer.CertPathProxy
import java.security.cert.CertPath
import java.security.cert.CertificateFactory
import java.util.function.Function
class CertPathDeserializer : Function<CertPathProxy, CertPath> {
override fun apply(proxy: CertPathProxy): CertPath {
val factory = CertificateFactory.getInstance(proxy.type)
return factory.generateCertPath(proxy.encoded.inputStream())
}
}

View File

@ -0,0 +1,11 @@
package net.corda.serialization.djvm.deserializers
import net.corda.serialization.internal.amqp.custom.ClassSerializer.ClassProxy
import java.util.function.Function
class ClassDeserializer : Function<ClassProxy, Class<*>> {
override fun apply(proxy: ClassProxy): Class<*> {
return Class.forName(proxy.className)
}
}

View File

@ -0,0 +1,50 @@
package net.corda.serialization.djvm.deserializers
import net.corda.core.utilities.NonEmptySet
import java.util.Collections
import java.util.NavigableSet
import java.util.SortedSet
import java.util.TreeSet
import java.util.function.Function
class CreateCollection : Function<Array<Any?>, Collection<Any?>> {
private val concreteConstructors: Map<Class<out Collection<*>>, (Array<Any?>) -> Collection<Any?>> = mapOf(
List::class.java to ::createList,
Set::class.java to ::createSet,
SortedSet::class.java to ::createSortedSet,
NavigableSet::class.java to ::createNavigableSet,
Collection::class.java to ::createCollection,
NonEmptySet::class.java to ::createNonEmptySet
)
private fun createList(values: Array<Any?>): List<Any?> {
return Collections.unmodifiableList(values.toCollection(ArrayList()))
}
private fun createSet(values: Array<Any?>): Set<Any?> {
return Collections.unmodifiableSet(values.toCollection(LinkedHashSet()))
}
private fun createSortedSet(values: Array<Any?>): SortedSet<Any?> {
return Collections.unmodifiableSortedSet(values.toCollection(TreeSet()))
}
private fun createNavigableSet(values: Array<Any?>): NavigableSet<Any?> {
return Collections.unmodifiableNavigableSet(values.toCollection(TreeSet()))
}
private fun createCollection(values: Array<Any?>): Collection<Any?> {
return Collections.unmodifiableCollection(values.toCollection(ArrayList()))
}
private fun createNonEmptySet(values: Array<Any?>): NonEmptySet<Any?> {
return NonEmptySet.copyOf(values.toCollection(ArrayList()))
}
@Suppress("unchecked_cast")
override fun apply(inputs: Array<Any?>): Collection<Any?> {
val collectionClass = inputs[0] as Class<out Collection<Any?>>
val args = inputs[1] as Array<Any?>
return concreteConstructors[collectionClass]?.invoke(args)!!
}
}

View File

@ -0,0 +1,10 @@
package net.corda.serialization.djvm.deserializers
import java.util.Currency
import java.util.function.Function
class CreateCurrency : Function<String, Currency> {
override fun apply(currencyCode: String): Currency {
return Currency.getInstance(currencyCode)
}
}

View File

@ -0,0 +1,10 @@
package net.corda.serialization.djvm.deserializers
import java.lang.reflect.Constructor
import java.util.function.Function
class CreateFromString(private val factory: Constructor<Any>) : Function<String, Any> {
override fun apply(text: String): Any {
return factory.newInstance(text)
}
}

View File

@ -0,0 +1,52 @@
package net.corda.serialization.djvm.deserializers
import java.util.Collections
import java.util.EnumMap
import java.util.NavigableMap
import java.util.SortedMap
import java.util.TreeMap
import java.util.function.Function
class CreateMap : Function<Array<Any>, Map<Any?, Any?>> {
private val concreteConstructors: Map<Class<out Map<*, *>>, (Array<Array<Any>>) -> Map<Any?, Any?>> = mapOf(
Map::class.java to ::createMap,
SortedMap::class.java to ::createSortedMap,
LinkedHashMap::class.java to ::createLinkedHashMap,
NavigableMap::class.java to ::createNavigableMap,
TreeMap::class.java to ::createTreeMap,
EnumMap::class.java to ::createEnumMap
)
private fun createMap(values: Array<Array<Any>>): Map<Any?, Any?> {
return Collections.unmodifiableMap(values.map { it[0] to it[1] }.toMap())
}
private fun createSortedMap(values: Array<Array<Any>>): SortedMap<Any?, out Any?> {
return Collections.unmodifiableSortedMap(createTreeMap(values))
}
private fun createNavigableMap(values: Array<Array<Any>>): NavigableMap<Any?, out Any?> {
return Collections.unmodifiableNavigableMap(createTreeMap(values))
}
private fun createLinkedHashMap(values: Array<Array<Any>>): LinkedHashMap<Any?, out Any?> {
return values.map { it[0] to it[1] }.toMap(LinkedHashMap())
}
private fun createTreeMap(values: Array<Array<Any>>): TreeMap<Any?, out Any?> {
return values.map { it[0] to it[1] }.toMap(TreeMap())
}
private fun createEnumMap(values: Array<Array<Any>>): Map<Any?, Any?> {
val map = values.map { it[0] to it[1] }.toMap()
@Suppress("unchecked_cast")
return EnumMap(map as Map<JustForCasting, Any?>) as Map<Any?, Any?>
}
@Suppress("unchecked_cast")
override fun apply(inputs: Array<Any>): Map<Any?, Any?> {
val mapClass = inputs[0] as Class<out Map<Any?, Any?>>
val args = inputs[1] as Array<Array<Any>>
return concreteConstructors[mapClass]?.invoke(args)!!
}
}

View File

@ -0,0 +1,10 @@
package net.corda.serialization.djvm.deserializers
import org.apache.qpid.proton.amqp.Decimal128
import java.util.function.Function
class Decimal128Deserializer : Function<LongArray, Decimal128> {
override fun apply(underlying: LongArray): Decimal128 {
return Decimal128(underlying[0], underlying[1])
}
}

View File

@ -0,0 +1,10 @@
package net.corda.serialization.djvm.deserializers
import org.apache.qpid.proton.amqp.Decimal32
import java.util.function.Function
class Decimal32Deserializer : Function<IntArray, Decimal32> {
override fun apply(underlying: IntArray): Decimal32 {
return Decimal32(underlying[0])
}
}

View File

@ -0,0 +1,10 @@
package net.corda.serialization.djvm.deserializers
import org.apache.qpid.proton.amqp.Decimal64
import java.util.function.Function
class Decimal64Deserializer : Function<LongArray, Decimal64> {
override fun apply(underlying: LongArray): Decimal64 {
return Decimal64(underlying[0])
}
}

View File

@ -0,0 +1,9 @@
package net.corda.serialization.djvm.deserializers
import java.util.function.Function
class DescribeEnum : Function<Class<*>, Array<out Any?>> {
override fun apply(enumClass: Class<*>): Array<out Any?> {
return enumClass.enumConstants
}
}

View File

@ -0,0 +1,11 @@
package net.corda.serialization.djvm.deserializers
import net.corda.serialization.internal.amqp.custom.DurationSerializer.DurationProxy
import java.time.Duration
import java.util.function.Function
class DurationDeserializer : Function<DurationProxy, Duration> {
override fun apply(proxy: DurationProxy): Duration {
return Duration.ofSeconds(proxy.seconds, proxy.nanos.toLong())
}
}

View File

@ -0,0 +1,16 @@
package net.corda.serialization.djvm.deserializers
import net.corda.core.internal.uncheckedCast
import net.corda.serialization.internal.amqp.custom.EnumSetSerializer.EnumSetProxy
import java.util.EnumSet
import java.util.function.Function
class EnumSetDeserializer : Function<EnumSetProxy, EnumSet<*>> {
override fun apply(proxy: EnumSetProxy): EnumSet<*> {
return if (proxy.elements.isEmpty()) {
EnumSet.noneOf(uncheckedCast<Class<*>, Class<JustForCasting>>(proxy.clazz))
} else {
EnumSet.copyOf(uncheckedCast<List<Any>, List<JustForCasting>>(proxy.elements))
}
}
}

View File

@ -0,0 +1,11 @@
package net.corda.serialization.djvm.deserializers
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.util.function.Function
class InputStreamDeserializer : Function<ByteArray, InputStream?> {
override fun apply(bytes: ByteArray): InputStream? {
return ByteArrayInputStream(bytes)
}
}

View File

@ -0,0 +1,11 @@
package net.corda.serialization.djvm.deserializers
import net.corda.serialization.internal.amqp.custom.InstantSerializer.InstantProxy
import java.time.Instant
import java.util.function.Function
class InstantDeserializer : Function<InstantProxy, Instant> {
override fun apply(proxy: InstantProxy): Instant {
return Instant.ofEpochSecond(proxy.epochSeconds, proxy.nanos.toLong())
}
}

View File

@ -0,0 +1,6 @@
package net.corda.serialization.djvm.deserializers
@Suppress("unused")
enum class JustForCasting {
UNUSED
}

View File

@ -0,0 +1,11 @@
package net.corda.serialization.djvm.deserializers
import net.corda.serialization.internal.amqp.custom.LocalDateSerializer.LocalDateProxy
import java.time.LocalDate
import java.util.function.Function
class LocalDateDeserializer : Function<LocalDateProxy, LocalDate> {
override fun apply(proxy: LocalDateProxy): LocalDate {
return LocalDate.of(proxy.year, proxy.month.toInt(), proxy.day.toInt())
}
}

View File

@ -0,0 +1,11 @@
package net.corda.serialization.djvm.deserializers
import net.corda.serialization.internal.amqp.custom.LocalDateTimeSerializer.LocalDateTimeProxy
import java.time.LocalDateTime
import java.util.function.Function
class LocalDateTimeDeserializer : Function<LocalDateTimeProxy, LocalDateTime> {
override fun apply(proxy: LocalDateTimeProxy): LocalDateTime {
return LocalDateTime.of(proxy.date, proxy.time)
}
}

View File

@ -0,0 +1,11 @@
package net.corda.serialization.djvm.deserializers
import net.corda.serialization.internal.amqp.custom.LocalTimeSerializer.LocalTimeProxy
import java.time.LocalTime
import java.util.function.Function
class LocalTimeDeserializer : Function<LocalTimeProxy, LocalTime> {
override fun apply(proxy: LocalTimeProxy): LocalTime {
return LocalTime.of(proxy.hour.toInt(), proxy.minute.toInt(), proxy.second.toInt(), proxy.nano)
}
}

View File

@ -0,0 +1,11 @@
package net.corda.serialization.djvm.deserializers
import net.corda.serialization.internal.amqp.custom.MonthDaySerializer.MonthDayProxy
import java.time.MonthDay
import java.util.function.Function
class MonthDayDeserializer : Function<MonthDayProxy, MonthDay> {
override fun apply(proxy: MonthDayProxy): MonthDay {
return MonthDay.of(proxy.month.toInt(), proxy.day.toInt())
}
}

View File

@ -0,0 +1,11 @@
package net.corda.serialization.djvm.deserializers
import net.corda.serialization.internal.amqp.custom.OffsetDateTimeSerializer.OffsetDateTimeProxy
import java.time.OffsetDateTime
import java.util.function.Function
class OffsetDateTimeDeserializer : Function<OffsetDateTimeProxy, OffsetDateTime> {
override fun apply(proxy: OffsetDateTimeProxy): OffsetDateTime {
return OffsetDateTime.of(proxy.dateTime, proxy.offset)
}
}

View File

@ -0,0 +1,11 @@
package net.corda.serialization.djvm.deserializers
import net.corda.serialization.internal.amqp.custom.OffsetTimeSerializer.OffsetTimeProxy
import java.time.OffsetTime
import java.util.function.Function
class OffsetTimeDeserializer : Function<OffsetTimeProxy, OffsetTime> {
override fun apply(proxy: OffsetTimeProxy): OffsetTime {
return OffsetTime.of(proxy.time, proxy.offset)
}
}

View File

@ -0,0 +1,11 @@
package net.corda.serialization.djvm.deserializers
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.OpaqueBytesSubSequence
import java.util.function.Function
class OpaqueBytesSubSequenceDeserializer : Function<OpaqueBytes, OpaqueBytesSubSequence> {
override fun apply(proxy: OpaqueBytes): OpaqueBytesSubSequence {
return OpaqueBytesSubSequence(proxy.bytes, proxy.offset, proxy.size)
}
}

View File

@ -0,0 +1,11 @@
package net.corda.serialization.djvm.deserializers
import net.corda.serialization.internal.amqp.custom.OptionalSerializer.OptionalProxy
import java.util.Optional
import java.util.function.Function
class OptionalDeserializer : Function<OptionalProxy, Optional<Any>> {
override fun apply(proxy: OptionalProxy): Optional<Any> {
return Optional.ofNullable(proxy.item)
}
}

View File

@ -0,0 +1,11 @@
package net.corda.serialization.djvm.deserializers
import net.corda.serialization.internal.amqp.custom.PeriodSerializer.PeriodProxy
import java.time.Period
import java.util.function.Function
class PeriodDeserializer : Function<PeriodProxy, Period> {
override fun apply(proxy: PeriodProxy): Period {
return Period.of(proxy.years, proxy.months, proxy.days)
}
}

View File

@ -0,0 +1,11 @@
package net.corda.serialization.djvm.deserializers
import net.corda.core.crypto.Crypto
import java.security.PublicKey
import java.util.function.Function
class PublicKeyDecoder : Function<ByteArray, PublicKey> {
override fun apply(encoded: ByteArray): PublicKey {
return Crypto.decodePublicKey(encoded)
}
}

View File

@ -0,0 +1,10 @@
package net.corda.serialization.djvm.deserializers
import org.apache.qpid.proton.amqp.Symbol
import java.util.function.Function
class SymbolDeserializer : Function<String, Symbol> {
override fun apply(value: String): Symbol {
return Symbol.valueOf(value)
}
}

View File

@ -0,0 +1,10 @@
package net.corda.serialization.djvm.deserializers
import org.apache.qpid.proton.amqp.UnsignedByte
import java.util.function.Function
class UnsignedByteDeserializer : Function<ByteArray, UnsignedByte> {
override fun apply(underlying: ByteArray): UnsignedByte {
return UnsignedByte(underlying[0])
}
}

View File

@ -0,0 +1,10 @@
package net.corda.serialization.djvm.deserializers
import org.apache.qpid.proton.amqp.UnsignedInteger
import java.util.function.Function
class UnsignedIntegerDeserializer : Function<IntArray, UnsignedInteger> {
override fun apply(underlying: IntArray): UnsignedInteger {
return UnsignedInteger(underlying[0])
}
}

View File

@ -0,0 +1,10 @@
package net.corda.serialization.djvm.deserializers
import org.apache.qpid.proton.amqp.UnsignedLong
import java.util.function.Function
class UnsignedLongDeserializer : Function<LongArray, UnsignedLong> {
override fun apply(underlying: LongArray): UnsignedLong {
return UnsignedLong(underlying[0])
}
}

View File

@ -0,0 +1,10 @@
package net.corda.serialization.djvm.deserializers
import org.apache.qpid.proton.amqp.UnsignedShort
import java.util.function.Function
class UnsignedShortDeserializer : Function<ShortArray, UnsignedShort> {
override fun apply(underlying: ShortArray): UnsignedShort {
return UnsignedShort(underlying[0])
}
}

View File

@ -0,0 +1,12 @@
package net.corda.serialization.djvm.deserializers
import java.security.cert.CertificateFactory
import java.security.cert.X509CRL
import java.util.function.Function
class X509CRLDeserializer : Function<ByteArray, X509CRL> {
override fun apply(bytes: ByteArray): X509CRL {
val factory = CertificateFactory.getInstance("X.509")
return factory.generateCRL(bytes.inputStream()) as X509CRL
}
}

View File

@ -0,0 +1,12 @@
package net.corda.serialization.djvm.deserializers
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.function.Function
class X509CertificateDeserializer : Function<ByteArray, X509Certificate> {
override fun apply(bits: ByteArray): X509Certificate {
val factory = CertificateFactory.getInstance("X.509")
return factory.generateCertificate(bits.inputStream()) as X509Certificate
}
}

View File

@ -0,0 +1,11 @@
package net.corda.serialization.djvm.deserializers
import net.corda.serialization.internal.amqp.custom.YearSerializer.YearProxy
import java.time.Year
import java.util.function.Function
class YearDeserializer : Function<YearProxy, Year> {
override fun apply(proxy: YearProxy): Year {
return Year.of(proxy.year)
}
}

View File

@ -0,0 +1,11 @@
package net.corda.serialization.djvm.deserializers
import net.corda.serialization.internal.amqp.custom.YearMonthSerializer.YearMonthProxy
import java.time.YearMonth
import java.util.function.Function
class YearMonthDeserializer : Function<YearMonthProxy, YearMonth> {
override fun apply(proxy: YearMonthProxy): YearMonth {
return YearMonth.of(proxy.year, proxy.month.toInt())
}
}

View File

@ -0,0 +1,11 @@
package net.corda.serialization.djvm.deserializers
import net.corda.serialization.internal.amqp.custom.ZoneIdSerializer.ZoneIdProxy
import java.time.ZoneId
import java.util.function.Function
class ZoneIdDeserializer : Function<ZoneIdProxy, ZoneId> {
override fun apply(proxy: ZoneIdProxy): ZoneId {
return ZoneId.of(proxy.id)
}
}

View File

@ -0,0 +1,10 @@
package net.corda.serialization.djvm.deserializers
import net.corda.serialization.internal.amqp.custom.ZonedDateTimeSerializer.ZonedDateTimeProxy
import java.util.function.Function
class ZonedDateTimeDeserializer : Function<ZonedDateTimeProxy, Array<out Any>?> {
override fun apply(proxy: ZonedDateTimeProxy): Array<out Any>? {
return arrayOf(proxy.dateTime, proxy.offset, proxy.zone)
}
}

View File

@ -0,0 +1,131 @@
package net.corda.serialization.djvm
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationContext.UseCase
import net.corda.core.serialization.SerializedBytes
import net.corda.core.utilities.ByteSequence
import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.serialization.djvm.serializers.SandboxBitSetSerializer
import net.corda.serialization.djvm.serializers.SandboxCertPathSerializer
import net.corda.serialization.djvm.serializers.SandboxCharacterSerializer
import net.corda.serialization.djvm.serializers.SandboxCollectionSerializer
import net.corda.serialization.djvm.serializers.SandboxCurrencySerializer
import net.corda.serialization.djvm.serializers.SandboxDecimal128Serializer
import net.corda.serialization.djvm.serializers.SandboxDecimal32Serializer
import net.corda.serialization.djvm.serializers.SandboxDecimal64Serializer
import net.corda.serialization.djvm.serializers.SandboxDurationSerializer
import net.corda.serialization.djvm.serializers.SandboxEnumSerializer
import net.corda.serialization.djvm.serializers.SandboxEnumSetSerializer
import net.corda.serialization.djvm.serializers.SandboxInputStreamSerializer
import net.corda.serialization.djvm.serializers.SandboxInstantSerializer
import net.corda.serialization.djvm.serializers.SandboxLocalDateSerializer
import net.corda.serialization.djvm.serializers.SandboxLocalDateTimeSerializer
import net.corda.serialization.djvm.serializers.SandboxLocalTimeSerializer
import net.corda.serialization.djvm.serializers.SandboxMapSerializer
import net.corda.serialization.djvm.serializers.SandboxMonthDaySerializer
import net.corda.serialization.djvm.serializers.SandboxOffsetDateTimeSerializer
import net.corda.serialization.djvm.serializers.SandboxOffsetTimeSerializer
import net.corda.serialization.djvm.serializers.SandboxOpaqueBytesSubSequenceSerializer
import net.corda.serialization.djvm.serializers.SandboxOptionalSerializer
import net.corda.serialization.djvm.serializers.SandboxPeriodSerializer
import net.corda.serialization.djvm.serializers.SandboxPrimitiveSerializer
import net.corda.serialization.djvm.serializers.SandboxPublicKeySerializer
import net.corda.serialization.djvm.serializers.SandboxSymbolSerializer
import net.corda.serialization.djvm.serializers.SandboxToStringSerializer
import net.corda.serialization.djvm.serializers.SandboxUnsignedByteSerializer
import net.corda.serialization.djvm.serializers.SandboxUnsignedIntegerSerializer
import net.corda.serialization.djvm.serializers.SandboxUnsignedLongSerializer
import net.corda.serialization.djvm.serializers.SandboxUnsignedShortSerializer
import net.corda.serialization.djvm.serializers.SandboxX509CRLSerializer
import net.corda.serialization.djvm.serializers.SandboxX509CertificateSerializer
import net.corda.serialization.djvm.serializers.SandboxYearMonthSerializer
import net.corda.serialization.djvm.serializers.SandboxYearSerializer
import net.corda.serialization.djvm.serializers.SandboxZoneIdSerializer
import net.corda.serialization.djvm.serializers.SandboxZonedDateTimeSerializer
import net.corda.serialization.internal.CordaSerializationMagic
import net.corda.serialization.internal.SerializationScheme
import net.corda.serialization.internal.amqp.DeserializationInput
import net.corda.serialization.internal.amqp.SerializationOutput
import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.amqp.SerializerFactoryFactory
import net.corda.serialization.internal.amqp.amqpMagic
import java.math.BigDecimal
import java.math.BigInteger
import java.util.Date
import java.util.UUID
import java.util.function.Function
class AMQPSerializationScheme(
private val classLoader: SandboxClassLoader,
private val sandboxBasicInput: Function<in Any?, out Any?>,
private val taskFactory: Function<in Any, out Function<in Any?, out Any?>>,
private val serializerFactoryFactory: SerializerFactoryFactory
) : SerializationScheme {
private fun getSerializerFactory(context: SerializationContext): SerializerFactory {
return serializerFactoryFactory.make(context).apply {
register(SandboxBitSetSerializer(classLoader, taskFactory, this))
register(SandboxCertPathSerializer(classLoader, taskFactory, this))
register(SandboxDurationSerializer(classLoader, taskFactory, this))
register(SandboxEnumSetSerializer(classLoader, taskFactory, this))
register(SandboxInputStreamSerializer(classLoader, taskFactory))
register(SandboxInstantSerializer(classLoader, taskFactory, this))
register(SandboxLocalDateSerializer(classLoader, taskFactory, this))
register(SandboxLocalDateTimeSerializer(classLoader, taskFactory, this))
register(SandboxLocalTimeSerializer(classLoader, taskFactory, this))
register(SandboxMonthDaySerializer(classLoader, taskFactory, this))
register(SandboxOffsetDateTimeSerializer(classLoader, taskFactory, this))
register(SandboxOffsetTimeSerializer(classLoader, taskFactory, this))
register(SandboxPeriodSerializer(classLoader, taskFactory, this))
register(SandboxYearMonthSerializer(classLoader, taskFactory, this))
register(SandboxYearSerializer(classLoader, taskFactory, this))
register(SandboxZonedDateTimeSerializer(classLoader, taskFactory, this))
register(SandboxZoneIdSerializer(classLoader, taskFactory, this))
register(SandboxOpaqueBytesSubSequenceSerializer(classLoader, taskFactory, this))
register(SandboxOptionalSerializer(classLoader, taskFactory, this))
register(SandboxPrimitiveSerializer(UUID::class.java, classLoader, sandboxBasicInput))
register(SandboxPrimitiveSerializer(String::class.java, classLoader, sandboxBasicInput))
register(SandboxPrimitiveSerializer(Byte::class.javaObjectType, classLoader, sandboxBasicInput))
register(SandboxPrimitiveSerializer(Short::class.javaObjectType, classLoader, sandboxBasicInput))
register(SandboxPrimitiveSerializer(Int::class.javaObjectType, classLoader, sandboxBasicInput))
register(SandboxPrimitiveSerializer(Long::class.javaObjectType, classLoader, sandboxBasicInput))
register(SandboxPrimitiveSerializer(Float::class.javaObjectType, classLoader, sandboxBasicInput))
register(SandboxPrimitiveSerializer(Double::class.javaObjectType, classLoader, sandboxBasicInput))
register(SandboxPrimitiveSerializer(Boolean::class.javaObjectType, classLoader, sandboxBasicInput))
register(SandboxPrimitiveSerializer(Date::class.javaObjectType, classLoader, sandboxBasicInput))
register(SandboxCharacterSerializer(classLoader, sandboxBasicInput))
register(SandboxCollectionSerializer(classLoader, taskFactory, this))
register(SandboxMapSerializer(classLoader, taskFactory, this))
register(SandboxEnumSerializer(classLoader, taskFactory, this))
register(SandboxPublicKeySerializer(classLoader, taskFactory))
register(SandboxToStringSerializer(BigDecimal::class.java, classLoader, taskFactory, sandboxBasicInput))
register(SandboxToStringSerializer(BigInteger::class.java, classLoader, taskFactory, sandboxBasicInput))
register(SandboxToStringSerializer(StringBuffer::class.java, classLoader, taskFactory, sandboxBasicInput))
register(SandboxCurrencySerializer(classLoader, taskFactory, sandboxBasicInput))
register(SandboxX509CertificateSerializer(classLoader, taskFactory))
register(SandboxX509CRLSerializer(classLoader, taskFactory))
register(SandboxUnsignedLongSerializer(classLoader, taskFactory))
register(SandboxUnsignedIntegerSerializer(classLoader, taskFactory))
register(SandboxUnsignedShortSerializer(classLoader, taskFactory))
register(SandboxUnsignedByteSerializer(classLoader, taskFactory))
register(SandboxDecimal128Serializer(classLoader, taskFactory))
register(SandboxDecimal64Serializer(classLoader, taskFactory))
register(SandboxDecimal32Serializer(classLoader, taskFactory))
register(SandboxSymbolSerializer(classLoader, taskFactory, sandboxBasicInput))
}
}
override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T {
val serializerFactory = getSerializerFactory(context)
return DeserializationInput(serializerFactory).deserialize(byteSequence, clazz, context)
}
override fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> {
val serializerFactory = getSerializerFactory(context)
return SerializationOutput(serializerFactory).serialize(obj, context)
}
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: UseCase): Boolean {
return magic == amqpMagic && target == UseCase.P2P
}
}

View File

@ -0,0 +1,10 @@
package net.corda.serialization.djvm
import net.corda.djvm.rewiring.SandboxClassLoader
class DelegatingClassLoader(private val delegate: SandboxClassLoader) : ClassLoader(null) {
@Throws(ClassNotFoundException::class)
override fun loadClass(name: String, resolve: Boolean): Class<*> {
return delegate.loadForSandbox(name).type
}
}

View File

@ -0,0 +1,108 @@
@file:Suppress("platform_class_mapped_to_kotlin")
package net.corda.serialization.djvm
import net.corda.core.serialization.SerializationContext
import net.corda.serialization.internal.amqp.AMQPRemoteTypeModel
import net.corda.serialization.internal.amqp.AMQPSerializer
import net.corda.serialization.internal.amqp.CachingCustomSerializerRegistry
import net.corda.serialization.internal.amqp.ComposedSerializerFactory
import net.corda.serialization.internal.amqp.DefaultDescriptorBasedSerializerRegistry
import net.corda.serialization.internal.amqp.DefaultEvolutionSerializerFactory
import net.corda.serialization.internal.amqp.DefaultLocalSerializerFactory
import net.corda.serialization.internal.amqp.DefaultRemoteSerializerFactory
import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.amqp.SerializerFactoryFactory
import net.corda.serialization.internal.amqp.WhitelistBasedTypeModelConfiguration
import net.corda.serialization.internal.amqp.createClassCarpenter
import net.corda.serialization.internal.model.ClassCarpentingTypeLoader
import net.corda.serialization.internal.model.ConfigurableLocalTypeModel
import net.corda.serialization.internal.model.SchemaBuildingRemoteTypeCarpenter
import net.corda.serialization.internal.model.TypeLoader
import net.corda.serialization.internal.model.TypeModellingFingerPrinter
import java.lang.Boolean
import java.lang.Byte
import java.lang.Double
import java.lang.Float
import java.lang.Long
import java.lang.Short
import java.util.Collections.singleton
import java.util.Collections.unmodifiableMap
import java.util.Date
import java.util.UUID
import java.util.function.Function
import java.util.function.Predicate
/**
* This has all been lovingly copied from [SerializerFactoryBuilder].
*/
class SandboxSerializerFactoryFactory(
private val primitiveSerializerFactory: Function<Class<*>, AMQPSerializer<Any>>
) : SerializerFactoryFactory {
override fun make(context: SerializationContext): SerializerFactory {
val classLoader = context.deserializationClassLoader
val primitiveTypes = unmodifiableMap(mapOf<Class<*>, Class<*>>(
classLoader.loadClass("sandbox.java.lang.Boolean") to Boolean.TYPE,
classLoader.loadClass("sandbox.java.lang.Byte") to Byte.TYPE,
classLoader.loadClass("sandbox.java.lang.Character") to Character.TYPE,
classLoader.loadClass("sandbox.java.lang.Double") to Double.TYPE,
classLoader.loadClass("sandbox.java.lang.Float") to Float.TYPE,
classLoader.loadClass("sandbox.java.lang.Integer") to Integer.TYPE,
classLoader.loadClass("sandbox.java.lang.Long") to Long.TYPE,
classLoader.loadClass("sandbox.java.lang.Short") to Short.TYPE,
classLoader.loadClass("sandbox.java.lang.String") to String::class.java,
classLoader.loadClass("sandbox.java.util.Date") to Date::class.java,
classLoader.loadClass("sandbox.java.util.UUID") to UUID::class.java,
Void::class.java to Void.TYPE
))
val classCarpenter = createClassCarpenter(context)
val descriptorBasedSerializerRegistry = DefaultDescriptorBasedSerializerRegistry()
val customSerializerRegistry = CachingCustomSerializerRegistry(
descriptorBasedSerializerRegistry = descriptorBasedSerializerRegistry,
allowedFor = singleton(classLoader.loadClass("sandbox.java.lang.Object"))
)
val localTypeModel = ConfigurableLocalTypeModel(
WhitelistBasedTypeModelConfiguration(context.whitelist, customSerializerRegistry)
)
val fingerPrinter = TypeModellingFingerPrinter(customSerializerRegistry)
val localSerializerFactory = DefaultLocalSerializerFactory(
whitelist = context.whitelist,
typeModel = localTypeModel,
fingerPrinter = fingerPrinter,
classloader = classLoader,
descriptorBasedSerializerRegistry = descriptorBasedSerializerRegistry,
primitiveSerializerFactory = primitiveSerializerFactory,
isPrimitiveType = Predicate { clazz -> clazz.isPrimitive || clazz in primitiveTypes.keys },
customSerializerRegistry = customSerializerRegistry,
onlyCustomSerializers = false
)
val typeLoader: TypeLoader = ClassCarpentingTypeLoader(
carpenter = SchemaBuildingRemoteTypeCarpenter(classCarpenter),
classLoader = classLoader
)
val evolutionSerializerFactory = DefaultEvolutionSerializerFactory(
localSerializerFactory = localSerializerFactory,
classLoader = classLoader,
mustPreserveDataWhenEvolving = context.preventDataLoss,
primitiveTypes = primitiveTypes
)
val remoteSerializerFactory = DefaultRemoteSerializerFactory(
evolutionSerializerFactory = evolutionSerializerFactory,
descriptorBasedSerializerRegistry = descriptorBasedSerializerRegistry,
remoteTypeModel = AMQPRemoteTypeModel(),
localTypeModel = localTypeModel,
typeLoader = typeLoader,
localSerializerFactory = localSerializerFactory
)
return ComposedSerializerFactory(localSerializerFactory, remoteSerializerFactory, customSerializerRegistry)
}
}

View File

@ -0,0 +1,13 @@
package net.corda.serialization.djvm
import net.corda.core.serialization.ClassWhitelist
class SandboxWhitelist : ClassWhitelist {
companion object {
private val packageName = "^sandbox\\.(?:java|kotlin)(?:[.]|$)".toRegex()
}
override fun hasListed(type: Class<*>): Boolean {
return packageName.containsMatchIn(type.`package`.name)
}
}

View File

@ -0,0 +1,74 @@
@file:JvmName("Serialization")
package net.corda.serialization.djvm
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationContext.UseCase
import net.corda.core.serialization.SerializationFactory
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.internal.SerializationEnvironment
import net.corda.core.utilities.ByteSequence
import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.serialization.djvm.serializers.PrimitiveSerializer
import net.corda.serialization.internal.GlobalTransientClassWhiteList
import net.corda.serialization.internal.SerializationContextImpl
import net.corda.serialization.internal.SerializationFactoryImpl
import net.corda.serialization.internal.amqp.AMQPSerializer
import net.corda.serialization.internal.amqp.amqpMagic
import java.util.function.Function
@Suppress("NOTHING_TO_INLINE")
inline fun SandboxClassLoader.toSandboxAnyClass(clazz: Class<*>): Class<Any> {
@Suppress("unchecked_cast")
return toSandboxClass(clazz) as Class<Any>
}
fun createSandboxSerializationEnv(classLoader: SandboxClassLoader): SerializationEnvironment {
val p2pContext: SerializationContext = SerializationContextImpl(
preferredSerializationVersion = amqpMagic,
deserializationClassLoader = DelegatingClassLoader(classLoader),
whitelist = GlobalTransientClassWhiteList(SandboxWhitelist()),
properties = emptyMap(),
objectReferencesEnabled = true,
carpenterDisabled = true,
useCase = UseCase.P2P,
encoding = null
)
val taskFactory = classLoader.createRawTaskFactory()
val sandboxBasicInput = classLoader.createBasicInput()
val primitiveSerializerFactory: Function<Class<*>, AMQPSerializer<Any>> = Function { clazz ->
PrimitiveSerializer(clazz, sandboxBasicInput)
}
val factory = SerializationFactoryImpl(mutableMapOf()).apply {
registerScheme(AMQPSerializationScheme(
classLoader = classLoader,
sandboxBasicInput = sandboxBasicInput,
taskFactory = taskFactory,
serializerFactoryFactory = SandboxSerializerFactoryFactory(primitiveSerializerFactory)
))
}
return SerializationEnvironment.with(factory, p2pContext = p2pContext)
}
inline fun <reified T: Any> SerializedBytes<T>.deserializeFor(classLoader: SandboxClassLoader): Any {
return deserializeTo(T::class.java, classLoader)
}
inline fun <reified T: Any> ByteSequence.deserializeTypeFor(classLoader: SandboxClassLoader): Any {
return deserializeTo(T::class.java, classLoader)
}
fun <T: Any> ByteSequence.deserializeTo(clazz: Class<T>, classLoader: SandboxClassLoader): Any {
val sandboxClazz = classLoader.toSandboxClass(clazz)
return deserializeTo(sandboxClazz)
}
fun ByteSequence.deserializeTo(clazz: Class<*>): Any {
return deserializeTo(clazz, SerializationFactory.defaultFactory)
}
fun ByteSequence.deserializeTo(clazz: Class<*>, factory: SerializationFactory): Any {
return factory.deserialize(this, clazz, factory.defaultContext)
}

View File

@ -0,0 +1,39 @@
@file:JvmName("ExceptionUtils")
package net.corda.serialization.djvm.serializers
import net.corda.serialization.internal.amqp.AMQPNotSerializableException
/**
* Utility function which helps tracking the path in the object graph when exceptions are thrown.
* Since there might be a chain of nested calls it is useful to record which part of the graph caused an issue.
* Path information is added to the message of the exception being thrown.
*/
@Suppress("TooGenericExceptionCaught")
internal inline fun <T> ifThrowsAppend(strToAppendFn: () -> String, block: () -> T): T {
try {
return block()
} catch (th: Throwable) {
when (th) {
is AMQPNotSerializableException -> th.classHierarchy.add(strToAppendFn())
// Do not overwrite the message of these exceptions as it may be used.
is ClassNotFoundException -> {}
is NoClassDefFoundError -> {}
else -> th.resetMessage("${strToAppendFn()} -> ${th.message}")
}
throw th
}
}
/**
* Not a public property so will have to use reflection
*/
private fun Throwable.resetMessage(newMsg: String) {
val detailMessageField = Throwable::class.java.getDeclaredField("detailMessage")
detailMessageField.isAccessible = true
detailMessageField.set(this, newMsg)
}
/**
* We currently only support deserialisation, and so we're going to need this.
*/
fun abortReadOnly(): Nothing = throw UnsupportedOperationException("Read Only!")

View File

@ -0,0 +1,36 @@
package net.corda.serialization.djvm.serializers
import net.corda.core.serialization.SerializationContext
import net.corda.serialization.internal.amqp.AMQPSerializer
import net.corda.serialization.internal.amqp.DeserializationInput
import net.corda.serialization.internal.amqp.SerializationOutput
import net.corda.serialization.internal.amqp.SerializationSchemas
import net.corda.serialization.internal.amqp.typeDescriptorFor
import org.apache.qpid.proton.amqp.Binary
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Type
import java.util.function.Function
class PrimitiveSerializer(
override val type: Class<*>,
private val sandboxBasicInput: Function<in Any?, out Any?>
) : AMQPSerializer<Any> {
override val typeDescriptor: Symbol = typeDescriptorFor(type)
override fun readObject(
obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext
): Any {
return (obj as? Binary)?.array ?: sandboxBasicInput.apply(obj)!!
}
override fun writeClassInfo(output: SerializationOutput) {
abortReadOnly()
}
override fun writeObject(
obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext, debugIndent: Int
) {
abortReadOnly()
}
}

View File

@ -0,0 +1,31 @@
package net.corda.serialization.djvm.serializers
import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.serialization.djvm.deserializers.BitSetDeserializer
import net.corda.serialization.djvm.toSandboxAnyClass
import net.corda.serialization.internal.amqp.CustomSerializer
import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.amqp.custom.BitSetSerializer.BitSetProxy
import java.util.BitSet
import java.util.Collections.singleton
import java.util.function.Function
class SandboxBitSetSerializer(
classLoader: SandboxClassLoader,
taskFactory: Function<in Any, out Function<in Any?, out Any?>>,
factory: SerializerFactory
) : CustomSerializer.Proxy<Any, Any>(
clazz = classLoader.toSandboxAnyClass(BitSet::class.java),
proxyClass = classLoader.toSandboxAnyClass(BitSetProxy::class.java),
factory = factory
) {
private val task = classLoader.createTaskFor(taskFactory, BitSetDeserializer::class.java)
override val deserializationAliases: Set<Class<*>> = singleton(BitSet::class.java)
override fun toProxy(obj: Any): Any = abortReadOnly()
override fun fromProxy(proxy: Any): Any {
return task.apply(proxy)!!
}
}

View File

@ -0,0 +1,31 @@
package net.corda.serialization.djvm.serializers
import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.serialization.djvm.deserializers.CertPathDeserializer
import net.corda.serialization.djvm.toSandboxAnyClass
import net.corda.serialization.internal.amqp.CustomSerializer
import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.amqp.custom.CertPathSerializer.CertPathProxy
import java.security.cert.CertPath
import java.util.Collections.singleton
import java.util.function.Function
class SandboxCertPathSerializer(
classLoader: SandboxClassLoader,
taskFactory: Function<in Any, out Function<in Any?, out Any?>>,
factory: SerializerFactory
) : CustomSerializer.Proxy<Any, Any>(
clazz = classLoader.toSandboxAnyClass(CertPath::class.java),
proxyClass = classLoader.toSandboxAnyClass(CertPathProxy::class.java),
factory = factory
) {
private val task = classLoader.createTaskFor(taskFactory, CertPathDeserializer::class.java)
override val deserializationAliases: Set<Class<*>> = singleton(CertPath::class.java)
override fun toProxy(obj: Any): Any = abortReadOnly()
override fun fromProxy(proxy: Any): Any {
return task.apply(proxy)!!
}
}

View File

@ -0,0 +1,37 @@
package net.corda.serialization.djvm.serializers
import net.corda.core.serialization.SerializationContext
import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.serialization.djvm.toSandboxAnyClass
import net.corda.serialization.internal.amqp.CustomSerializer
import net.corda.serialization.internal.amqp.DeserializationInput
import net.corda.serialization.internal.amqp.Schema
import net.corda.serialization.internal.amqp.SerializationOutput
import net.corda.serialization.internal.amqp.SerializationSchemas
import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Type
import java.util.function.Function
class SandboxCharacterSerializer(
classLoader: SandboxClassLoader,
private val basicInput: Function<in Any?, out Any?>
) : CustomSerializer.Is<Any>(classLoader.toSandboxAnyClass(Char::class.javaObjectType)) {
override val schemaForDocumentation: Schema = Schema(emptyList())
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any {
return basicInput.apply(convertToChar(obj))!!
}
private fun convertToChar(obj: Any): Any {
return when (obj) {
is Short -> obj.toChar()
is Int -> obj.toChar()
else -> obj
}
}
override fun writeDescribedObject(obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext) {
abortReadOnly()
}
}

View File

@ -0,0 +1,47 @@
package net.corda.serialization.djvm.serializers
import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.serialization.djvm.deserializers.ClassDeserializer
import net.corda.serialization.djvm.toSandboxAnyClass
import net.corda.serialization.internal.amqp.AMQPNotSerializableException
import net.corda.serialization.internal.amqp.CustomSerializer
import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.amqp.custom.ClassSerializer.ClassProxy
import java.util.function.Function
@Suppress("unchecked_cast")
class SandboxClassSerializer(
classLoader: SandboxClassLoader,
taskFactory: Function<in Any, out Function<in Any?, out Any?>>,
factory: SerializerFactory
) : CustomSerializer.Proxy<Any, Any>(
clazz = Class::class.java as Class<Any>,
proxyClass = classLoader.toSandboxAnyClass(ClassProxy::class.java),
factory = factory
) {
private val task = classLoader.createTaskFor(taskFactory, ClassDeserializer::class.java)
private val nameOf: Function<Any, String>
init {
val fetch = proxyClass.getMethod("getClassName")
nameOf = Function { proxy ->
fetch(proxy).toString()
}
}
override fun toProxy(obj: Any): Any = abortReadOnly()
override fun fromProxy(proxy: Any): Any {
return try {
task.apply(proxy)!!
} catch (e: ClassNotFoundException) {
val className = nameOf.apply(proxy)
throw AMQPNotSerializableException(type,
"Could not instantiate $className - not on the classpath",
"$className was not found by the node, check the Node containing the CorDapp that " +
"implements $className is loaded and on the Classpath",
mutableListOf(className)
)
}
}
}

View File

@ -0,0 +1,129 @@
package net.corda.serialization.djvm.serializers
import net.corda.core.serialization.SerializationContext
import net.corda.core.utilities.NonEmptySet
import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.serialization.djvm.deserializers.CreateCollection
import net.corda.serialization.djvm.toSandboxAnyClass
import net.corda.serialization.internal.amqp.AMQPSerializer
import net.corda.serialization.internal.amqp.CustomSerializer
import net.corda.serialization.internal.amqp.DeserializationInput
import net.corda.serialization.internal.amqp.LocalSerializerFactory
import net.corda.serialization.internal.amqp.Schema
import net.corda.serialization.internal.amqp.SerializationOutput
import net.corda.serialization.internal.amqp.SerializationSchemas
import net.corda.serialization.internal.amqp.redescribe
import net.corda.serialization.internal.model.LocalTypeInformation
import net.corda.serialization.internal.model.TypeIdentifier
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.util.EnumSet
import java.util.NavigableSet
import java.util.SortedSet
import java.util.function.Function
class SandboxCollectionSerializer(
classLoader: SandboxClassLoader,
taskFactory: Function<in Any, out Function<in Any?, out Any?>>,
private val localFactory: LocalSerializerFactory
) : CustomSerializer.Implements<Any>(clazz = classLoader.toSandboxAnyClass(Collection::class.java)) {
@Suppress("unchecked_cast")
private val creator: Function<Array<Any>, out Any?>
= classLoader.createTaskFor(taskFactory, CreateCollection::class.java) as Function<Array<Any>, out Any?>
private val unsupportedTypes: Set<Class<Any>> = listOf(
EnumSet::class.java
).map {
classLoader.toSandboxAnyClass(it)
}.toSet()
// The order matters here - the first match should be the most specific one.
// Kotlin preserves the ordering for us by associating into a LinkedHashMap.
private val supportedTypes: Map<Class<Any>, Class<out Collection<*>>> = listOf(
List::class.java,
NonEmptySet::class.java,
NavigableSet::class.java,
SortedSet::class.java,
Set::class.java,
Collection::class.java
).associateBy {
classLoader.toSandboxAnyClass(it)
}
private fun getBestMatchFor(type: Class<Any>): Map.Entry<Class<Any>, Class<out Collection<*>>>
= supportedTypes.entries.first { it.key.isAssignableFrom(type) }
override val schemaForDocumentation: Schema = Schema(emptyList())
override fun isSerializerFor(clazz: Class<*>): Boolean {
return super.isSerializerFor(clazz) && unsupportedTypes.none { it.isAssignableFrom(clazz) }
}
override fun specialiseFor(declaredType: Type): AMQPSerializer<Any>? {
if (declaredType !is ParameterizedType) {
return null
}
@Suppress("unchecked_cast")
val rawType = declaredType.rawType as Class<Any>
return ConcreteCollectionSerializer(declaredType, getBestMatchFor(rawType), creator, localFactory)
}
override fun readObject(
obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext
): Any {
throw UnsupportedOperationException("Factory only")
}
override fun writeDescribedObject(
obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext
) {
throw UnsupportedOperationException("Factory Only")
}
}
private class ConcreteCollectionSerializer(
declaredType: ParameterizedType,
private val matchingType: Map.Entry<Class<Any>, Class<out Collection<*>>>,
private val creator: Function<Array<Any>, out Any?>,
factory: LocalSerializerFactory
) : AMQPSerializer<Any> {
override val type: ParameterizedType = declaredType
override val typeDescriptor: Symbol by lazy {
factory.createDescriptor(
LocalTypeInformation.ACollection(
observedType = declaredType.rawType,
typeIdentifier = TypeIdentifier.forGenericType(declaredType),
elementType =factory.getTypeInformation(declaredType.actualTypeArguments[0])
)
)
}
override fun readObject(
obj: Any,
schemas: SerializationSchemas,
input: DeserializationInput,
context: SerializationContext
): Any {
val inboundType = type.actualTypeArguments[0]
return ifThrowsAppend({ type.typeName }) {
val args = (obj as List<*>).map {
input.readObjectOrNull(redescribe(it, inboundType), schemas, inboundType, context)
}.toTypedArray()
creator.apply(arrayOf(matchingType.key, args))!!
}
}
override fun writeClassInfo(output: SerializationOutput) {
abortReadOnly()
}
override fun writeObject(
obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext, debugIndent: Int
) {
abortReadOnly()
}
}

Some files were not shown because too many files have changed in this diff Show More