mirror of
https://github.com/corda/corda.git
synced 2024-12-24 07:06:44 +00:00
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:
parent
f9d8084d44
commit
f226ddc4f2
@ -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>)
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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>()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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?,
|
||||
|
@ -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)
|
@ -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 {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
24
node/djvm/build.gradle
Normal 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()
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
51
node/djvm/src/main/kotlin/net/corda/node/djvm/LtxFactory.kt
Normal file
51
node/djvm/src/main/kotlin/net/corda/node/djvm/LtxFactory.kt
Normal 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)
|
||||
}
|
||||
}
|
0
node/djvm/src/main/resources/META-INF/DJVM-preload
Normal file
0
node/djvm/src/main/resources/META-INF/DJVM-preload
Normal 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
|
||||
}
|
@ -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()
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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, ")
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
))
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
@ -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)
|
||||
}
|
||||
}
|
@ -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) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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]))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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() {}
|
||||
}
|
@ -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*'
|
||||
|
69
serialization-djvm/build.gradle
Normal file
69
serialization-djvm/build.gradle
Normal 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
|
||||
}
|
||||
}
|
34
serialization-djvm/deserializers/build.gradle
Normal file
34
serialization-djvm/deserializers/build.gradle
Normal 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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)!!
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)!!
|
||||
}
|
||||
}
|
@ -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])
|
||||
}
|
||||
}
|
@ -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])
|
||||
}
|
||||
}
|
@ -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])
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package net.corda.serialization.djvm.deserializers
|
||||
|
||||
@Suppress("unused")
|
||||
enum class JustForCasting {
|
||||
UNUSED
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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])
|
||||
}
|
||||
}
|
@ -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])
|
||||
}
|
||||
}
|
@ -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])
|
||||
}
|
||||
}
|
@ -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])
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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!")
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)!!
|
||||
}
|
||||
}
|
@ -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)!!
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
Loading…
Reference in New Issue
Block a user