mirror of
https://github.com/corda/corda.git
synced 2024-12-20 05:28:21 +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()
|
public abstract java.util.List<net.corda.core.contracts.StateAndRef<net.corda.core.contracts.ContractState>> getReferences()
|
||||||
##
|
##
|
||||||
@DoNotImplement
|
@DoNotImplement
|
||||||
@CordaSerializable
|
|
||||||
public final class net.corda.core.transactions.LedgerTransaction extends net.corda.core.transactions.FullTransaction
|
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)
|
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)
|
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
|
@NotNull
|
||||||
public final java.util.List<net.corda.core.contracts.Command<T>> commandsOfType(Class<T>)
|
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.disruptor_version = constants.getProperty("disruptorVersion")
|
||||||
ext.metrics_version = constants.getProperty("metricsVersion")
|
ext.metrics_version = constants.getProperty("metricsVersion")
|
||||||
ext.metrics_new_relic_version = constants.getProperty("metricsNewRelicVersion")
|
ext.metrics_new_relic_version = constants.getProperty("metricsNewRelicVersion")
|
||||||
|
ext.djvm_version = constants.getProperty("djvmVersion")
|
||||||
ext.okhttp_version = '3.14.2'
|
ext.okhttp_version = '3.14.2'
|
||||||
ext.netty_version = '4.1.22.Final'
|
ext.netty_version = '4.1.22.Final'
|
||||||
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
|
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
|
||||||
@ -335,6 +336,7 @@ allprojects {
|
|||||||
jcenter()
|
jcenter()
|
||||||
maven { url "$artifactory_contextUrl/corda-dependencies" }
|
maven { url "$artifactory_contextUrl/corda-dependencies" }
|
||||||
maven { url 'https://repo.gradle.org/gradle/libs-releases' }
|
maven { url 'https://repo.gradle.org/gradle/libs-releases' }
|
||||||
|
maven { url "$artifactory_contextUrl/corda-dev" }
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
@ -481,11 +483,13 @@ bintrayConfig {
|
|||||||
'corda-core',
|
'corda-core',
|
||||||
'corda-core-deterministic',
|
'corda-core-deterministic',
|
||||||
'corda-deterministic-verifier',
|
'corda-deterministic-verifier',
|
||||||
|
'corda-deserializers-djvm',
|
||||||
'corda',
|
'corda',
|
||||||
'corda-finance-workflows',
|
'corda-finance-workflows',
|
||||||
'corda-finance-contracts',
|
'corda-finance-contracts',
|
||||||
'corda-node',
|
'corda-node',
|
||||||
'corda-node-api',
|
'corda-node-api',
|
||||||
|
'corda-node-djvm',
|
||||||
'corda-test-common',
|
'corda-test-common',
|
||||||
'corda-test-utils',
|
'corda-test-utils',
|
||||||
'corda-test-db',
|
'corda-test-db',
|
||||||
@ -498,6 +502,7 @@ bintrayConfig {
|
|||||||
'corda-tools-shell-cli',
|
'corda-tools-shell-cli',
|
||||||
'corda-serialization',
|
'corda-serialization',
|
||||||
'corda-serialization-deterministic',
|
'corda-serialization-deterministic',
|
||||||
|
'corda-serialization-djvm',
|
||||||
'corda-tools-blob-inspector',
|
'corda-tools-blob-inspector',
|
||||||
'corda-tools-explorer',
|
'corda-tools-explorer',
|
||||||
'corda-tools-network-bootstrapper',
|
'corda-tools-network-bootstrapper',
|
||||||
|
@ -13,11 +13,10 @@ java8MinUpdateVersion=171
|
|||||||
platformVersion=5
|
platformVersion=5
|
||||||
guavaVersion=28.0-jre
|
guavaVersion=28.0-jre
|
||||||
# Quasar version to use with Java 8:
|
# Quasar version to use with Java 8:
|
||||||
quasarVersion=0.7.10
|
quasarVersion=0.7.12_r3
|
||||||
quasarClassifier=jdk8
|
quasarClassifier=jdk8
|
||||||
# Quasar version to use with Java 11:
|
# Quasar version to use with Java 11:
|
||||||
quasarVersion11=0.8.0
|
quasarVersion11=0.8.0_r3_rc1
|
||||||
# Specify a classifier for Java 11 built artifacts
|
|
||||||
jdkClassifier11=jdk11
|
jdkClassifier11=jdk11
|
||||||
proguardVersion=6.1.1
|
proguardVersion=6.1.1
|
||||||
bouncycastleVersion=1.60
|
bouncycastleVersion=1.60
|
||||||
@ -30,6 +29,7 @@ snakeYamlVersion=1.19
|
|||||||
caffeineVersion=2.7.0
|
caffeineVersion=2.7.0
|
||||||
metricsVersion=4.1.0
|
metricsVersion=4.1.0
|
||||||
metricsNewRelicVersion=1.1.1
|
metricsNewRelicVersion=1.1.1
|
||||||
|
djvmVersion=1.0-RC01
|
||||||
openSourceBranch=https://github.com/corda/corda/blob/master
|
openSourceBranch=https://github.com/corda/corda/blob/master
|
||||||
openSourceSamplesBranch=https://github.com/corda/samples/blob/master
|
openSourceSamplesBranch=https://github.com/corda/samples/blob/master
|
||||||
jolokiaAgentVersion=1.6.1
|
jolokiaAgentVersion=1.6.1
|
||||||
|
@ -56,6 +56,7 @@ task patchCore(type: Zip, dependsOn: coreJarTask) {
|
|||||||
archiveExtension = 'jar'
|
archiveExtension = 'jar'
|
||||||
|
|
||||||
from(compileKotlin)
|
from(compileKotlin)
|
||||||
|
from(processResources)
|
||||||
from(zipTree(originalJar)) {
|
from(zipTree(originalJar)) {
|
||||||
exclude 'net/corda/core/internal/*ToggleField*.class'
|
exclude 'net/corda/core/internal/*ToggleField*.class'
|
||||||
exclude 'net/corda/core/serialization/*SerializationFactory*.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.crypto.SecureHash.Companion.zeroHash
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.accessLeafIndex
|
|
||||||
import net.corda.core.node.NotaryInfo
|
import net.corda.core.node.NotaryInfo
|
||||||
import net.corda.core.node.services.IdentityService
|
import net.corda.core.node.services.IdentityService
|
||||||
import net.corda.core.serialization.deserialize
|
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")))
|
val pmt = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("1"), SecureHash.sha256("5"), SecureHash.sha256("0"), SecureHash.sha256("19")))
|
||||||
// First leaf.
|
// First leaf.
|
||||||
assertEquals(0, pmt.accessLeafIndex(SecureHash.sha256("0")))
|
assertEquals(0, pmt.leafIndex(SecureHash.sha256("0")))
|
||||||
// Second leaf.
|
// Second leaf.
|
||||||
assertEquals(1, pmt.accessLeafIndex(SecureHash.sha256("1")))
|
assertEquals(1, pmt.leafIndex(SecureHash.sha256("1")))
|
||||||
// A random leaf.
|
// A random leaf.
|
||||||
assertEquals(5, pmt.accessLeafIndex(SecureHash.sha256("5")))
|
assertEquals(5, pmt.leafIndex(SecureHash.sha256("5")))
|
||||||
// The last leaf.
|
// 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.
|
// 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).
|
// 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")))
|
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.
|
// 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")))
|
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.
|
// 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")))
|
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.
|
// 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)
|
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).
|
// 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
|
@Test
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package net.corda.core.crypto
|
package net.corda.core.crypto
|
||||||
|
|
||||||
import net.corda.core.CordaException
|
import net.corda.core.CordaException
|
||||||
|
import net.corda.core.CordaInternal
|
||||||
import net.corda.core.KeepForDJVM
|
import net.corda.core.KeepForDJVM
|
||||||
import net.corda.core.crypto.SecureHash.Companion.zeroHash
|
import net.corda.core.crypto.SecureHash.Companion.zeroHash
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
@ -169,8 +170,9 @@ class PartialMerkleTree(val root: PartialTree) {
|
|||||||
* @return leaf-index of this component (starting from zero).
|
* @return leaf-index of this component (starting from zero).
|
||||||
* @throws MerkleTreeException if the provided hash is not in the tree.
|
* @throws MerkleTreeException if the provided hash is not in the tree.
|
||||||
*/
|
*/
|
||||||
|
@CordaInternal
|
||||||
@Throws(MerkleTreeException::class)
|
@Throws(MerkleTreeException::class)
|
||||||
internal fun leafIndex(leaf: SecureHash): Int {
|
fun leafIndex(leaf: SecureHash): Int {
|
||||||
// Special handling if the tree consists of one node only.
|
// Special handling if the tree consists of one node only.
|
||||||
if (root is PartialTree.IncludedLeaf && root.hash == leaf) return 0
|
if (root is PartialTree.IncludedLeaf && root.hash == leaf) return 0
|
||||||
val flagPath = mutableListOf<Boolean>()
|
val flagPath = mutableListOf<Boolean>()
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
package net.corda.core.internal
|
package net.corda.core.internal
|
||||||
|
|
||||||
import net.corda.core.DeleteForDJVM
|
import net.corda.core.DeleteForDJVM
|
||||||
|
import net.corda.core.KeepForDJVM
|
||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.contracts.TransactionVerificationException.TransactionContractConflictException
|
import net.corda.core.contracts.TransactionVerificationException.TransactionContractConflictException
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.internal.rules.StateContractValidationEnforcementRule
|
import net.corda.core.internal.rules.StateContractValidationEnforcementRule
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
@DeleteForDJVM
|
@DeleteForDJVM
|
||||||
interface TransactionVerifierServiceInternal {
|
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
|
* 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.
|
* 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 inputStates: List<TransactionState<*>> = ltx.inputs.map { it.state }
|
||||||
private val allStates: List<TransactionState<*>> = inputStates + ltx.references.map { it.state } + ltx.outputs
|
private val allStates: List<TransactionState<*>> = inputStates + ltx.references.map { it.state } + ltx.outputs
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = contextLogger()
|
val logger = contextLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -140,7 +141,7 @@ class Verifier(val ltx: LedgerTransaction, private val transactionClassLoader: C
|
|||||||
.withIndex()
|
.withIndex()
|
||||||
.filter { it.value.encumbrance != null }
|
.filter { it.value.encumbrance != null }
|
||||||
.map { Pair(it.index, it.value.encumbrance!!) }
|
.map { Pair(it.index, it.value.encumbrance!!) }
|
||||||
if (!statesAndEncumbrance.isEmpty()) {
|
if (statesAndEncumbrance.isNotEmpty()) {
|
||||||
checkBidirectionalOutputEncumbrances(statesAndEncumbrance)
|
checkBidirectionalOutputEncumbrances(statesAndEncumbrance)
|
||||||
checkNotariesOutputEncumbrance(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.
|
* 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.
|
* If any contract fails to verify, the whole transaction is considered to be invalid.
|
||||||
*
|
*
|
||||||
* Note: Reference states are not verified.
|
* Note: Reference states are not verified.
|
||||||
*/
|
*/
|
||||||
private fun verifyContracts() {
|
override fun verifyContracts() {
|
||||||
|
try {
|
||||||
// Loads the contract class from the transactionClassLoader.
|
ContractVerifier(transactionClassLoader).apply(ltx)
|
||||||
fun contractClassFor(className: ContractClassName) = try {
|
} catch (e: TransactionVerificationException.ContractRejection) {
|
||||||
transactionClassLoader.loadClass(className).asSubclass(Contract::class.java)
|
logger.error("Error validating transaction ${ltx.id}.", e.cause)
|
||||||
} catch (e: Exception) {
|
throw e
|
||||||
throw TransactionVerificationException.ContractCreationError(ltx.id, className, e)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val contractClasses: Map<ContractClassName, Class<out Contract>> = (inputStates + ltx.outputs)
|
/**
|
||||||
.map { it.contract }
|
* Verify all of the contracts on the given [LedgerTransaction].
|
||||||
.toSet()
|
*/
|
||||||
.map { contract -> contract to contractClassFor(contract) }
|
@Suppress("TooGenericExceptionCaught")
|
||||||
.toMap()
|
@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 {
|
try {
|
||||||
/**
|
/**
|
||||||
* This function must execute with the DJVM's sandbox, which does not
|
* 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) {
|
} catch (e: Exception) {
|
||||||
throw TransactionVerificationException.ContractCreationError(ltx.id, contractClassName, e)
|
throw TransactionVerificationException.ContractCreationError(ltx.id, contractClassName, e)
|
||||||
}
|
}
|
||||||
}
|
}.forEach { contract ->
|
||||||
|
|
||||||
contractInstances.forEach { contract ->
|
|
||||||
try {
|
try {
|
||||||
contract.verify(ltx)
|
contract.verify(ltx)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.error("Error validating transaction ${ltx.id}.", e)
|
|
||||||
throw TransactionVerificationException.ContractRejection(ltx.id, contract, 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.node.services.*
|
||||||
import net.corda.core.serialization.SerializeAsToken
|
import net.corda.core.serialization.SerializeAsToken
|
||||||
import net.corda.core.transactions.FilteredTransaction
|
import net.corda.core.transactions.FilteredTransaction
|
||||||
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
@ -71,6 +72,12 @@ interface ServicesForResolution {
|
|||||||
*/
|
*/
|
||||||
@Throws(TransactionResolutionException::class, AttachmentResolutionException::class)
|
@Throws(TransactionResolutionException::class, AttachmentResolutionException::class)
|
||||||
fun loadContractAttachment(stateRef: StateRef): Attachment
|
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
|
package net.corda.core.node.services
|
||||||
|
|
||||||
import net.corda.core.DoNotImplement
|
import net.corda.core.DoNotImplement
|
||||||
|
import net.corda.core.KeepForDJVM
|
||||||
import net.corda.core.contracts.Attachment
|
import net.corda.core.contracts.Attachment
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
|
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.io.InputStream
|
||||||
import java.net.*
|
import java.net.*
|
||||||
import java.util.*
|
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
|
* 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,
|
isAttachmentTrusted: (Attachment) -> Boolean,
|
||||||
parent: ClassLoader = ClassLoader.getSystemClassLoader(),
|
parent: ClassLoader = ClassLoader.getSystemClassLoader(),
|
||||||
block: (ClassLoader) -> T): T {
|
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)) {
|
val serializationContext = cache.computeIfAbsent(Key(attachmentIds, params)) {
|
||||||
// Create classloader and load serializers, whitelisted classes
|
// Create classloader and load serializers, whitelisted classes
|
||||||
val transactionClassLoader = AttachmentsClassLoader(attachments, params, txId, isAttachmentTrusted, parent)
|
val transactionClassLoader = AttachmentsClassLoader(attachments, params, txId, isAttachmentTrusted, parent)
|
||||||
val serializers = createInstancesOfClassesImplementing(transactionClassLoader, SerializationCustomSerializer::class.java)
|
val serializers = createInstancesOfClassesImplementing(transactionClassLoader, SerializationCustomSerializer::class.java)
|
||||||
val whitelistedClasses = ServiceLoader.load(SerializationWhitelist::class.java, transactionClassLoader)
|
val whitelistedClasses = ServiceLoader.load(SerializationWhitelist::class.java, transactionClassLoader)
|
||||||
.flatMap { it.whitelist }
|
.flatMap(SerializationWhitelist::whitelist)
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
// Create a new serializationContext for the current transaction. In this context we will forbid
|
// 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.CordaInternal
|
||||||
import net.corda.core.KeepForDJVM
|
import net.corda.core.KeepForDJVM
|
||||||
import net.corda.core.StubOutForDJVM
|
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.crypto.SecureHash
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.identity.Party
|
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.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.serialization.internal.AttachmentsClassLoaderBuilder
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import java.util.*
|
import java.util.Collections.unmodifiableList
|
||||||
import java.util.function.Predicate
|
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
|
* [LedgerTransaction]s should never be instantiated directly from client code, but rather via WireTransaction.toLedgerTransaction
|
||||||
*/
|
*/
|
||||||
|
@Suppress("LongParameterList")
|
||||||
@KeepForDJVM
|
@KeepForDJVM
|
||||||
@CordaSerializable
|
|
||||||
class LedgerTransaction
|
class LedgerTransaction
|
||||||
@ConstructorForDeserialization
|
|
||||||
private constructor(
|
private constructor(
|
||||||
// DOCSTART 1
|
// DOCSTART 1
|
||||||
/** The resolved input states which will be consumed/invalidated by the execution of this transaction. */
|
/** 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?,
|
override val networkParameters: NetworkParameters?,
|
||||||
/** Referenced states, which are like inputs but won't be consumed. */
|
/** 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
|
//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() {
|
) : 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 {
|
init {
|
||||||
if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" }
|
if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" }
|
||||||
checkNotaryWhitelisted()
|
checkNotaryWhitelisted()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@KeepForDJVM
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = contextLogger()
|
private val logger = contextLogger()
|
||||||
|
|
||||||
|
private fun <T> protect(list: List<T>?): List<T>? {
|
||||||
|
return list?.run {
|
||||||
|
if (isEmpty()) {
|
||||||
|
emptyList()
|
||||||
|
} else {
|
||||||
|
unmodifiableList(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@CordaInternal
|
@CordaInternal
|
||||||
internal fun create(
|
internal fun create(
|
||||||
inputs: List<StateAndRef<ContractState>>,
|
inputs: List<StateAndRef<ContractState>>,
|
||||||
@ -101,12 +126,58 @@ private constructor(
|
|||||||
serializedReferences: List<SerializedStateAndRef>? = null,
|
serializedReferences: List<SerializedStateAndRef>? = null,
|
||||||
isAttachmentTrusted: (Attachment) -> Boolean
|
isAttachmentTrusted: (Attachment) -> Boolean
|
||||||
): LedgerTransaction {
|
): LedgerTransaction {
|
||||||
return LedgerTransaction(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references).apply {
|
return LedgerTransaction(
|
||||||
this.componentGroups = componentGroups
|
inputs = inputs,
|
||||||
this.serializedInputs = serializedInputs
|
outputs = outputs,
|
||||||
this.serializedReferences = serializedReferences
|
commands = commands,
|
||||||
this.isAttachmentTrusted = isAttachmentTrusted
|
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
|
// 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.
|
// like no-overlap, package namespace ownership and (in future) deterministic Java.
|
||||||
return AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(
|
return AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(
|
||||||
this.attachments + extraAttachments,
|
attachments + extraAttachments,
|
||||||
getParamsWithGoo(),
|
getParamsWithGoo(),
|
||||||
id,
|
id,
|
||||||
isAttachmentTrusted = isAttachmentTrusted) { transactionClassLoader ->
|
isAttachmentTrusted = isAttachmentTrusted) { transactionClassLoader ->
|
||||||
// Create a copy of the outer LedgerTransaction which deserializes all fields inside the [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.
|
// Only the copy will be used for verification, and the outer shell will be discarded.
|
||||||
// This artifice is required to preserve backwards compatibility.
|
// 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.
|
// Read network parameters with backwards compatibility goo.
|
||||||
private fun getParamsWithGoo(): NetworkParameters {
|
private fun getParamsWithGoo(): NetworkParameters {
|
||||||
var params = networkParameters
|
var params = networkParameters
|
||||||
@ -213,7 +315,12 @@ private constructor(
|
|||||||
timeWindow = this.timeWindow,
|
timeWindow = this.timeWindow,
|
||||||
privacySalt = this.privacySalt,
|
privacySalt = this.privacySalt,
|
||||||
networkParameters = this.networkParameters,
|
networkParameters = this.networkParameters,
|
||||||
references = deserializedReferences
|
references = deserializedReferences,
|
||||||
|
componentGroups = componentGroups,
|
||||||
|
serializedInputs = serializedInputs,
|
||||||
|
serializedReferences = serializedReferences,
|
||||||
|
isAttachmentTrusted = isAttachmentTrusted,
|
||||||
|
verifierFactory = verifierFactory
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// This branch is only present for backwards compatibility.
|
// This branch is only present for backwards compatibility.
|
||||||
@ -582,10 +689,25 @@ private constructor(
|
|||||||
notary: Party?,
|
notary: Party?,
|
||||||
timeWindow: TimeWindow?,
|
timeWindow: TimeWindow?,
|
||||||
privacySalt: PrivacySalt
|
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.")
|
@Deprecated("LedgerTransaction should not be created directly, use WireTransaction.toLedgerTransaction instead.")
|
||||||
@DeprecatedConstructorForDeserialization(1)
|
|
||||||
constructor(
|
constructor(
|
||||||
inputs: List<StateAndRef<ContractState>>,
|
inputs: List<StateAndRef<ContractState>>,
|
||||||
outputs: List<TransactionState<ContractState>>,
|
outputs: List<TransactionState<ContractState>>,
|
||||||
@ -596,7 +718,23 @@ private constructor(
|
|||||||
timeWindow: TimeWindow?,
|
timeWindow: TimeWindow?,
|
||||||
privacySalt: PrivacySalt,
|
privacySalt: PrivacySalt,
|
||||||
networkParameters: NetworkParameters
|
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.")
|
@Deprecated("LedgerTransactions should not be created directly, use WireTransaction.toLedgerTransaction instead.")
|
||||||
fun copy(inputs: List<StateAndRef<ContractState>>,
|
fun copy(inputs: List<StateAndRef<ContractState>>,
|
||||||
@ -618,7 +756,12 @@ private constructor(
|
|||||||
timeWindow = timeWindow,
|
timeWindow = timeWindow,
|
||||||
privacySalt = privacySalt,
|
privacySalt = privacySalt,
|
||||||
networkParameters = networkParameters,
|
networkParameters = networkParameters,
|
||||||
references = references
|
references = references,
|
||||||
|
componentGroups = componentGroups,
|
||||||
|
serializedInputs = serializedInputs,
|
||||||
|
serializedReferences = serializedReferences,
|
||||||
|
isAttachmentTrusted = isAttachmentTrusted,
|
||||||
|
verifierFactory = verifierFactory
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -643,7 +786,12 @@ private constructor(
|
|||||||
timeWindow = timeWindow,
|
timeWindow = timeWindow,
|
||||||
privacySalt = privacySalt,
|
privacySalt = privacySalt,
|
||||||
networkParameters = networkParameters,
|
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 fun defaultLockId() = (Strand.currentStrand() as? FlowStateMachine<*>)?.id?.uuid ?: UUID.randomUUID()
|
||||||
private val log = contextLogger()
|
private val log = contextLogger()
|
||||||
|
|
||||||
private val ID_PATTERN = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"
|
private const val ID_PATTERN = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"
|
||||||
private val FQCP = Pattern.compile("$ID_PATTERN(/$ID_PATTERN)+")
|
private val FQCP: Pattern = Pattern.compile("$ID_PATTERN(/$ID_PATTERN)+")
|
||||||
private fun isValidJavaClass(identifier: String) = FQCP.matcher(identifier).matches()
|
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)
|
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
|
||||||
@DeleteForDJVM
|
@DeleteForDJVM
|
||||||
fun toLedgerTransaction(services: ServicesForResolution): LedgerTransaction {
|
fun toLedgerTransaction(services: ServicesForResolution): LedgerTransaction {
|
||||||
return toLedgerTransactionInternal(
|
return services.specialise(
|
||||||
|
toLedgerTransactionInternal(
|
||||||
resolveIdentity = { services.identityService.partyFromKey(it) },
|
resolveIdentity = { services.identityService.partyFromKey(it) },
|
||||||
resolveAttachment = { services.attachments.openAttachment(it) },
|
resolveAttachment = { services.attachments.openAttachment(it) },
|
||||||
resolveStateRefAsSerialized = { resolveStateRefBinaryComponent(it, services) },
|
resolveStateRefAsSerialized = { resolveStateRefBinaryComponent(it, services) },
|
||||||
@ -109,6 +110,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
|||||||
},
|
},
|
||||||
// `as?` is used due to [MockServices] not implementing [ServiceHubCoreInternal]
|
// `as?` is used due to [MockServices] not implementing [ServiceHubCoreInternal]
|
||||||
isAttachmentTrusted = { (services as? ServiceHubCoreInternal)?.attachmentTrustCalculator?.calculate(it) ?: true }
|
isAttachmentTrusted = { (services as? ServiceHubCoreInternal)?.attachmentTrustCalculator?.calculate(it) ?: true }
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +131,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
|||||||
* @throws AttachmentResolutionException if a required attachment was not found using [resolveAttachment].
|
* @throws AttachmentResolutionException if a required attachment was not found using [resolveAttachment].
|
||||||
* @throws TransactionResolutionException if an input was not found not using [resolveStateRef].
|
* @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)
|
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
|
||||||
fun toLedgerTransaction(
|
fun toLedgerTransaction(
|
||||||
resolveIdentity: (PublicKey) -> Party?,
|
resolveIdentity: (PublicKey) -> Party?,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package net.corda.core.internal
|
package net.corda.core.internal
|
||||||
|
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.PartialMerkleTree
|
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.NetworkParameters
|
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 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 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'
|
description 'Corda node modules'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
// Extra repository for the deterministic-rt JAR.
|
||||||
|
maven {
|
||||||
|
url "$artifactory_contextUrl/corda-dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//noinspection GroovyAssignabilityCheck
|
//noinspection GroovyAssignabilityCheck
|
||||||
configurations {
|
configurations {
|
||||||
integrationTestCompile.extendsFrom testCompile
|
integrationTestCompile.extendsFrom testCompile
|
||||||
@ -38,6 +45,11 @@ configurations {
|
|||||||
|
|
||||||
slowIntegrationTestCompile.extendsFrom testCompile
|
slowIntegrationTestCompile.extendsFrom testCompile
|
||||||
slowIntegrationTestRuntimeOnly.extendsFrom testRuntimeOnly
|
slowIntegrationTestRuntimeOnly.extendsFrom testRuntimeOnly
|
||||||
|
|
||||||
|
jdkRt.resolutionStrategy {
|
||||||
|
cacheChangingModulesFor 0, 'seconds'
|
||||||
|
}
|
||||||
|
deterministic
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@ -156,6 +168,19 @@ dependencies {
|
|||||||
// TypeSafe Config: for simple and human friendly config files.
|
// TypeSafe Config: for simple and human friendly config files.
|
||||||
compile "com.typesafe:config:$typesafe_config_version"
|
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 "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
|
||||||
testImplementation "junit:junit:$junit_version"
|
testImplementation "junit:junit:$junit_version"
|
||||||
|
|
||||||
@ -245,8 +270,11 @@ tasks.withType(JavaCompile) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(Test) {
|
tasks.withType(Test) {
|
||||||
if (JavaVersion.current() == JavaVersion.VERSION_11)
|
if (JavaVersion.current() == JavaVersion.VERSION_11) {
|
||||||
jvmArgs '-Djdk.attach.allowAttachSelf=true'
|
jvmArgs '-Djdk.attach.allowAttachSelf=true'
|
||||||
|
}
|
||||||
|
systemProperty 'deterministic-rt.path', configurations.jdkRt.asPath
|
||||||
|
systemProperty 'deterministic-sources.path', configurations.deterministic.asPath
|
||||||
}
|
}
|
||||||
|
|
||||||
task integrationTest(type: Test) {
|
task integrationTest(type: Test) {
|
||||||
@ -263,6 +291,9 @@ task slowIntegrationTest(type: Test) {
|
|||||||
|
|
||||||
// quasar exclusions upon agent code instrumentation at run-time
|
// quasar exclusions upon agent code instrumentation at run-time
|
||||||
quasar {
|
quasar {
|
||||||
|
excludeClassLoaders.addAll(
|
||||||
|
'net.corda.djvm.**'
|
||||||
|
)
|
||||||
excludePackages.addAll(
|
excludePackages.addAll(
|
||||||
"antlr**",
|
"antlr**",
|
||||||
"com.codahale**",
|
"com.codahale**",
|
||||||
@ -271,10 +302,12 @@ quasar {
|
|||||||
"com.google.**",
|
"com.google.**",
|
||||||
"com.lmax.**",
|
"com.lmax.**",
|
||||||
"com.zaxxer.**",
|
"com.zaxxer.**",
|
||||||
|
"djvm**",
|
||||||
"net.bytebuddy**",
|
"net.bytebuddy**",
|
||||||
"io.github.classgraph**",
|
"io.github.classgraph**",
|
||||||
"io.netty*",
|
"io.netty*",
|
||||||
"liquibase**",
|
"liquibase**",
|
||||||
|
"net.corda.djvm**",
|
||||||
"net.i2p.crypto.**",
|
"net.i2p.crypto.**",
|
||||||
"nonapi.io.github.classgraph.**",
|
"nonapi.io.github.classgraph.**",
|
||||||
"org.apiguardian.**",
|
"org.apiguardian.**",
|
||||||
@ -292,6 +325,10 @@ quasar {
|
|||||||
|
|
||||||
jar {
|
jar {
|
||||||
baseName 'corda-node'
|
baseName 'corda-node'
|
||||||
|
manifest {
|
||||||
|
attributes('Corda-Deterministic-Runtime': configurations.jdkRt.singleFile.name)
|
||||||
|
attributes('Corda-Deterministic-Classpath': configurations.deterministic.collect { it.name }.join(' '))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
publish {
|
publish {
|
||||||
|
@ -8,6 +8,8 @@ apply plugin: 'com.jfrog.artifactory'
|
|||||||
|
|
||||||
description 'Corda standalone node'
|
description 'Corda standalone node'
|
||||||
|
|
||||||
|
evaluationDependsOn(':node')
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
runtimeArtifacts.extendsFrom runtimeClasspath
|
runtimeArtifacts.extendsFrom runtimeClasspath
|
||||||
capsuleRuntime
|
capsuleRuntime
|
||||||
@ -34,28 +36,64 @@ capsule {
|
|||||||
version capsule_version
|
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'
|
applicationClass 'net.corda.node.Corda'
|
||||||
archiveBaseName = 'corda'
|
archiveBaseName = 'corda'
|
||||||
archiveVersion = corda_release_version
|
archiveVersion = corda_release_version
|
||||||
archiveClassifier = jdkClassifier
|
archiveClassifier = jdkClassifier
|
||||||
archiveName = archiveFileName.get()
|
archiveName = archiveFileName.get()
|
||||||
applicationSource = files(
|
applicationSource = files(
|
||||||
project(':node').configurations.runtimeClasspath,
|
nodeProject.configurations.runtimeClasspath,
|
||||||
project(':node').tasks.jar,
|
nodeProject.tasks.jar,
|
||||||
project(':node').buildDir.toString() + '/resources/main/reference.conf',
|
nodeProject.buildDir.toString() + '/resources/main/reference.conf',
|
||||||
"$rootDir/config/dev/log4j2.xml",
|
"$rootDir/config/dev/log4j2.xml",
|
||||||
'NOTICE' // Copy CDDL notice
|
'NOTICE' // Copy CDDL notice
|
||||||
)
|
)
|
||||||
from configurations.capsuleRuntime.files.collect { zipTree(it) }
|
from configurations.capsuleRuntime.files.collect { zipTree(it) }
|
||||||
with jar
|
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 {
|
capsuleManifest {
|
||||||
applicationVersion = corda_release_version
|
applicationVersion = corda_release_version
|
||||||
applicationId = "net.corda.node.Corda"
|
applicationId = "net.corda.node.Corda"
|
||||||
// See experimental/quasar-hook/README.md for how to generate.
|
// 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**)"
|
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**)"
|
||||||
javaAgents = quasar_classifier == null ? ["quasar-core-${quasar_version}.jar=${quasarExcludeExpression}"] : ["quasar-core-${quasar_version}-${quasar_classifier}.jar=${quasarExcludeExpression}"]
|
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'
|
systemProperties['visualvm.display.name'] = 'Corda'
|
||||||
if (JavaVersion.current() == JavaVersion.VERSION_1_8) {
|
if (JavaVersion.current() == JavaVersion.VERSION_1_8) {
|
||||||
minJavaVersion = '1.8.0'
|
minJavaVersion = '1.8.0'
|
||||||
@ -70,8 +108,9 @@ task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').tasks.jar) {
|
|||||||
//
|
//
|
||||||
// If you change these flags, please also update Driver.kt
|
// If you change these flags, please also update Driver.kt
|
||||||
jvmArgs = ['-Xmx512m', '-XX:+UseG1GC']
|
jvmArgs = ['-Xmx512m', '-XX:+UseG1GC']
|
||||||
if (JavaVersion.current() == JavaVersion.VERSION_11)
|
if (JavaVersion.current() == JavaVersion.VERSION_11) {
|
||||||
jvmArgs += ['-Djdk.attach.allowAttachSelf=true']
|
jvmArgs += ['-Djdk.attach.allowAttachSelf=true']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,12 +6,22 @@ import com.typesafe.config.*;
|
|||||||
import sun.misc.Signal;
|
import sun.misc.Signal;
|
||||||
|
|
||||||
import java.io.File;
|
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.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.jar.JarInputStream;
|
||||||
|
import java.util.jar.Manifest;
|
||||||
import java.util.stream.Stream;
|
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 {
|
public class CordaCaplet extends Capsule {
|
||||||
|
private static final String DJVM_DIR ="djvm";
|
||||||
|
|
||||||
private Config nodeConfig = null;
|
private Config nodeConfig = null;
|
||||||
private String baseDir = null;
|
private String baseDir = null;
|
||||||
@ -79,10 +89,76 @@ public class CordaCaplet extends Capsule {
|
|||||||
return null;
|
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
|
@Override
|
||||||
protected ProcessBuilder prelaunch(List<String> jvmArgs, List<String> args) {
|
protected ProcessBuilder prelaunch(List<String> jvmArgs, List<String> args) {
|
||||||
checkJavaVersion();
|
checkJavaVersion();
|
||||||
nodeConfig = parseConfigFile(args);
|
nodeConfig = parseConfigFile(args);
|
||||||
|
installDJVM();
|
||||||
return super.prelaunch(jvmArgs, args);
|
return super.prelaunch(jvmArgs, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,10 +231,13 @@ public class CordaCaplet extends Capsule {
|
|||||||
// Add system properties, if specified, from the config.
|
// Add system properties, if specified, from the config.
|
||||||
Map<String, String> systemProps = new LinkedHashMap<>((Map<String, String>) super.attribute(attr));
|
Map<String, String> systemProps = new LinkedHashMap<>((Map<String, String>) super.attribute(attr));
|
||||||
try {
|
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);
|
log(LOG_VERBOSE, "Configured system properties = " + overrideSystemProps);
|
||||||
for (Map.Entry<String, ConfigValue> entry : overrideSystemProps.entrySet()) {
|
for (Map.Entry<String, ?> entry : overrideSystemProps.entrySet()) {
|
||||||
systemProps.put(entry.getKey(), entry.getValue().unwrapped().toString());
|
systemProps.put(entry.getKey(), entry.getValue().toString());
|
||||||
}
|
}
|
||||||
} catch (ConfigException.Missing e) {
|
} catch (ConfigException.Missing e) {
|
||||||
// Ignore since it's ok to be Missing. Other errors would be unexpected.
|
// 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) {
|
private Boolean isJAR(File file) {
|
||||||
return file.getName().toLowerCase().endsWith(".jar");
|
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.SecureHash
|
||||||
import net.corda.core.crypto.internal.AliasPrivateKey
|
import net.corda.core.crypto.internal.AliasPrivateKey
|
||||||
import net.corda.core.crypto.newSecureRandom
|
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.AbstractParty
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.identity.PartyAndCertificate
|
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.doneFuture
|
||||||
import net.corda.core.internal.concurrent.map
|
import net.corda.core.internal.concurrent.map
|
||||||
import net.corda.core.internal.concurrent.openFuture
|
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.messaging.InternalCordaRPCOps
|
||||||
import net.corda.core.internal.notary.NotaryService
|
import net.corda.core.internal.notary.NotaryService
|
||||||
import net.corda.core.messaging.*
|
import net.corda.core.internal.rootMessage
|
||||||
import net.corda.core.node.*
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.node.services.*
|
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.schemas.MappedSchema
|
||||||
import net.corda.core.serialization.SerializationWhitelist
|
import net.corda.core.serialization.SerializationWhitelist
|
||||||
import net.corda.core.serialization.SerializeAsToken
|
import net.corda.core.serialization.SerializeAsToken
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.days
|
import net.corda.core.utilities.days
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.core.utilities.minutes
|
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.CordaClock
|
||||||
import net.corda.node.VersionInfo
|
import net.corda.node.VersionInfo
|
||||||
import net.corda.node.internal.classloading.requireAnnotation
|
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.AuthenticatedRpcOpsProxy
|
||||||
import net.corda.node.internal.rpc.proxies.ThreadContextAdjustingRpcOpsProxy
|
import net.corda.node.internal.rpc.proxies.ThreadContextAdjustingRpcOpsProxy
|
||||||
import net.corda.node.services.ContractUpgradeHandler
|
import net.corda.node.services.ContractUpgradeHandler
|
||||||
import net.corda.node.services.FinalityHandler
|
import net.corda.node.services.FinalityHandler
|
||||||
import net.corda.node.services.NotaryChangeHandler
|
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.attachments.NodeAttachmentTrustCalculator
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
import net.corda.node.services.config.configureWithDevSSLCertificate
|
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.NetworkMapUpdater
|
||||||
import net.corda.node.services.network.NodeInfoWatcher
|
import net.corda.node.services.network.NodeInfoWatcher
|
||||||
import net.corda.node.services.network.PersistentNetworkMapCache
|
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.rpc.CheckpointDumper
|
||||||
import net.corda.node.services.schema.NodeSchemaService
|
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.InMemoryTransactionVerifierService
|
||||||
import net.corda.node.services.transactions.SimpleNotaryService
|
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.upgrade.ContractUpgradeServiceImpl
|
||||||
import net.corda.node.services.vault.NodeVaultService
|
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.NodeInfoAndSigned
|
||||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||||
import net.corda.nodeapi.internal.config.CertificateStore
|
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.CryptoServiceFactory
|
||||||
import net.corda.nodeapi.internal.cryptoservice.SupportedCryptoServices
|
import net.corda.nodeapi.internal.cryptoservice.SupportedCryptoServices
|
||||||
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
|
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 net.corda.tools.shell.InteractiveShell
|
||||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||||
import org.jolokia.jvmagent.JolokiaServer
|
import org.jolokia.jvmagent.JolokiaServer
|
||||||
@ -103,13 +175,15 @@ import java.sql.Connection
|
|||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.format.DateTimeParseException
|
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.ExecutorService
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.TimeUnit.MINUTES
|
import java.util.concurrent.TimeUnit.MINUTES
|
||||||
import java.util.concurrent.TimeUnit.SECONDS
|
import java.util.concurrent.TimeUnit.SECONDS
|
||||||
import java.util.function.Consumer
|
import java.util.function.Consumer
|
||||||
import javax.persistence.EntityManager
|
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
|
* 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 versionInfo: VersionInfo,
|
||||||
protected val flowManager: FlowManager,
|
protected val flowManager: FlowManager,
|
||||||
val serverThread: AffinityExecutor.ServiceAffinityExecutor,
|
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
|
protected abstract val log: Logger
|
||||||
@Suppress("LeakingThis")
|
@Suppress("LeakingThis")
|
||||||
@ -214,6 +290,18 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
).closeOnStop()
|
).closeOnStop()
|
||||||
@Suppress("LeakingThis")
|
@Suppress("LeakingThis")
|
||||||
val transactionVerifierService = InMemoryTransactionVerifierService(transactionVerifierWorkerCount).tokenize()
|
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 contractUpgradeService = ContractUpgradeServiceImpl(cacheFactory).tokenize()
|
||||||
val auditService = DummyAuditService().tokenize()
|
val auditService = DummyAuditService().tokenize()
|
||||||
@Suppress("LeakingThis")
|
@Suppress("LeakingThis")
|
||||||
@ -326,7 +414,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
open fun start(): S {
|
open fun start(): S {
|
||||||
check(started == null) { "Node has already been started" }
|
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")
|
System.setProperty("co.paralleluniverse.fibers.verifyInstrumentation", "true")
|
||||||
}
|
}
|
||||||
log.info("Node starting up ...")
|
log.info("Node starting up ...")
|
||||||
@ -1069,6 +1157,11 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
override fun registerUnloadHandler(runOnStop: () -> Unit) {
|
override fun registerUnloadHandler(runOnStop: () -> Unit) {
|
||||||
this@AbstractNode.runOnStop += runOnStop
|
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.MetricRegistry
|
||||||
import com.codahale.metrics.jmx.JmxReporter
|
import com.codahale.metrics.jmx.JmxReporter
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
|
import com.jcabi.manifests.Manifests
|
||||||
import com.palominolabs.metrics.newrelic.AllEnabledMetricAttributeFilter
|
import com.palominolabs.metrics.newrelic.AllEnabledMetricAttributeFilter
|
||||||
import com.palominolabs.metrics.newrelic.NewRelicReporter
|
import com.palominolabs.metrics.newrelic.NewRelicReporter
|
||||||
import io.netty.util.NettyRuntime
|
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.div
|
||||||
import net.corda.core.internal.errors.AddressBindingException
|
import net.corda.core.internal.errors.AddressBindingException
|
||||||
import net.corda.core.internal.getJavaUpdateVersion
|
import net.corda.core.internal.getJavaUpdateVersion
|
||||||
|
import net.corda.core.internal.isRegularFile
|
||||||
import net.corda.core.internal.notary.NotaryService
|
import net.corda.core.internal.notary.NotaryService
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.messaging.RPCOps
|
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.serialization.internal.nodeSerializationEnv
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.contextLogger
|
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.CordaClock
|
||||||
import net.corda.node.SimpleClock
|
import net.corda.node.SimpleClock
|
||||||
import net.corda.node.VersionInfo
|
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.FlowStarter
|
||||||
import net.corda.node.services.api.ServiceHubInternal
|
import net.corda.node.services.api.ServiceHubInternal
|
||||||
import net.corda.node.services.api.StartedNodeServices
|
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.ArtemisMessagingServer
|
||||||
import net.corda.node.services.messaging.MessagingService
|
import net.corda.node.services.messaging.MessagingService
|
||||||
import net.corda.node.services.messaging.P2PMessagingClient
|
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.InternalRPCMessagingClient
|
||||||
import net.corda.node.services.rpc.RPCServerConfiguration
|
import net.corda.node.services.rpc.RPCServerConfiguration
|
||||||
import net.corda.node.services.statemachine.StateMachineManager
|
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.ArtemisMessagingClient
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_SHELL_USER
|
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.config.User
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
|
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.SerializationFactoryCacheKey
|
||||||
import net.corda.serialization.internal.amqp.SerializerFactory
|
import net.corda.serialization.internal.amqp.SerializerFactory
|
||||||
import org.apache.commons.lang3.SystemUtils
|
import org.apache.commons.lang3.SystemUtils
|
||||||
@ -75,6 +96,7 @@ import java.lang.Long.max
|
|||||||
import java.lang.Long.min
|
import java.lang.Long.min
|
||||||
import java.net.BindException
|
import java.net.BindException
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
@ -99,7 +121,9 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
versionInfo: VersionInfo,
|
versionInfo: VersionInfo,
|
||||||
private val initialiseSerialization: Boolean = true,
|
private val initialiseSerialization: Boolean = true,
|
||||||
flowManager: FlowManager = NodeFlowManager(configuration.flowOverrides),
|
flowManager: FlowManager = NodeFlowManager(configuration.flowOverrides),
|
||||||
cacheFactoryPrototype: BindableNamedCacheFactory = DefaultNamedCacheFactory()
|
cacheFactoryPrototype: BindableNamedCacheFactory = DefaultNamedCacheFactory(),
|
||||||
|
djvmBootstrapSource: ApiSource = createBootstrapSource(configuration),
|
||||||
|
djvmCordaSource: UserSource? = createCordaSource(configuration)
|
||||||
) : AbstractNode<NodeInfo>(
|
) : AbstractNode<NodeInfo>(
|
||||||
configuration,
|
configuration,
|
||||||
createClock(configuration),
|
createClock(configuration),
|
||||||
@ -107,13 +131,19 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
versionInfo,
|
versionInfo,
|
||||||
flowManager,
|
flowManager,
|
||||||
// Under normal (non-test execution) it will always be "1"
|
// 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 =
|
override fun createStartedNode(nodeInfo: NodeInfo, rpcOps: CordaRPCOps, notaryService: NotaryService?): NodeInfo =
|
||||||
nodeInfo
|
nodeInfo
|
||||||
|
|
||||||
companion object {
|
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()
|
private val staticLog = contextLogger()
|
||||||
var renderBasicInfoToConsole = true
|
var renderBasicInfoToConsole = true
|
||||||
|
|
||||||
@ -172,6 +202,74 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
false
|
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
|
override val log: Logger get() = staticLog
|
||||||
|
@ -35,7 +35,7 @@ data class ServicesForResolutionImpl(
|
|||||||
return stateRefs.groupBy { it.txhash }.flatMap {
|
return stateRefs.groupBy { it.txhash }.flatMap {
|
||||||
val stx = validatedTransactions.getTransaction(it.key) ?: throw TransactionResolutionException(it.key)
|
val stx = validatedTransactions.getTransaction(it.key) ?: throw TransactionResolutionException(it.key)
|
||||||
val baseTx = stx.resolveBaseTransaction(this)
|
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()
|
}.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)
|
.withFallback(defaultConfig)
|
||||||
.resolve()
|
.resolve()
|
||||||
|
|
||||||
val entrySet = finalConfig.entrySet().filter { entry -> entry.key.contains("\"") }
|
val entrySet = finalConfig.entrySet().filter { entry ->
|
||||||
for ((key) in entrySet) {
|
// 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")
|
log.error("Config files should not contain \" in property names. Please fix: $key")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,8 +68,10 @@ object ConfigHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun Config.cordaEntriesOnly(): Config {
|
private fun Config.cordaEntriesOnly(): Config {
|
||||||
|
return ConfigFactory.parseMap(toProperties()
|
||||||
return ConfigFactory.parseMap(toProperties().filterKeys { (it as String).startsWith(CORDA_PROPERTY_PREFIX) }.mapKeys { (it.key as String).removePrefix(CORDA_PROPERTY_PREFIX) })
|
.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
|
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 {
|
internal object Defaults {
|
||||||
|
|
||||||
val disableCheckpointChecker = false
|
val disableCheckpointChecker = false
|
||||||
val allowCompatibilityZone = false
|
val allowCompatibilityZone = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class DJVMOptions(
|
||||||
|
val bootstrapSource: String?,
|
||||||
|
val cordaSource: List<String>
|
||||||
|
)
|
||||||
|
|
||||||
fun NodeConfiguration.shouldCheckCheckpoints(): Boolean {
|
fun NodeConfiguration.shouldCheckCheckpoints(): Boolean {
|
||||||
return this.devMode && this.devModeOptions?.disableCheckpointChecker != true
|
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.AuthDataSourceType
|
||||||
import net.corda.node.services.config.CertChainPolicyConfig
|
import net.corda.node.services.config.CertChainPolicyConfig
|
||||||
import net.corda.node.services.config.CertChainPolicyType
|
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.DevModeOptions
|
||||||
import net.corda.node.services.config.FlowOverride
|
import net.corda.node.services.config.FlowOverride
|
||||||
import net.corda.node.services.config.FlowOverrideConfig
|
import net.corda.node.services.config.FlowOverrideConfig
|
||||||
@ -127,9 +128,19 @@ internal object SecurityConfigurationSpec : Configuration.Specification<Security
|
|||||||
internal object DevModeOptionsSpec : Configuration.Specification<DevModeOptions>("DevModeOptions") {
|
internal object DevModeOptionsSpec : Configuration.Specification<DevModeOptions>("DevModeOptions") {
|
||||||
private val disableCheckpointChecker by boolean().optional().withDefaultValue(DevModeOptions.Defaults.disableCheckpointChecker)
|
private val disableCheckpointChecker by boolean().optional().withDefaultValue(DevModeOptions.Defaults.disableCheckpointChecker)
|
||||||
private val allowCompatibilityZone by boolean().optional().withDefaultValue(DevModeOptions.Defaults.allowCompatibilityZone)
|
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> {
|
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'
|
archiveExtension = 'jar'
|
||||||
|
|
||||||
from(compileKotlin)
|
from(compileKotlin)
|
||||||
|
from(processResources)
|
||||||
from(zipTree(originalJar)) {
|
from(zipTree(originalJar)) {
|
||||||
exclude 'net/corda/serialization/internal/AttachmentsClassLoaderBuilder*'
|
exclude 'net/corda/serialization/internal/AttachmentsClassLoaderBuilder*'
|
||||||
exclude 'net/corda/serialization/internal/ByteBufferStreams*'
|
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