mirror of
https://github.com/corda/corda.git
synced 2024-12-22 14:22:28 +00:00
Merge pull request #7 from corda/tudor_merge_os_master
Tudor merge os master
This commit is contained in:
commit
020a292383
@ -2532,7 +2532,6 @@ public interface net.corda.core.messaging.CordaRPCOps extends net.corda.core.mes
|
|||||||
public abstract void clearNetworkMapCache()
|
public abstract void clearNetworkMapCache()
|
||||||
@NotNull
|
@NotNull
|
||||||
public abstract java.time.Instant currentNodeTime()
|
public abstract java.time.Instant currentNodeTime()
|
||||||
public abstract int getProtocolVersion()
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public abstract Iterable<String> getVaultTransactionNotes(net.corda.core.crypto.SecureHash)
|
public abstract Iterable<String> getVaultTransactionNotes(net.corda.core.crypto.SecureHash)
|
||||||
@RPCReturnsObservables
|
@RPCReturnsObservables
|
||||||
@ -4449,8 +4448,6 @@ public interface net.corda.core.serialization.SerializationCustomSerializer
|
|||||||
public abstract PROXY toProxy(OBJ)
|
public abstract PROXY toProxy(OBJ)
|
||||||
##
|
##
|
||||||
public final class net.corda.core.serialization.SerializationDefaults extends java.lang.Object
|
public final class net.corda.core.serialization.SerializationDefaults extends java.lang.Object
|
||||||
@NotNull
|
|
||||||
public final net.corda.core.serialization.SerializationContext getCHECKPOINT_CONTEXT()
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public final net.corda.core.serialization.SerializationContext getP2P_CONTEXT()
|
public final net.corda.core.serialization.SerializationContext getP2P_CONTEXT()
|
||||||
@NotNull
|
@NotNull
|
||||||
@ -6887,8 +6884,6 @@ public final class net.corda.testing.core.SerializationEnvironmentRule extends j
|
|||||||
@NotNull
|
@NotNull
|
||||||
public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement, org.junit.runner.Description)
|
public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement, org.junit.runner.Description)
|
||||||
@NotNull
|
@NotNull
|
||||||
public final net.corda.core.serialization.SerializationContext getCheckpointContext()
|
|
||||||
@NotNull
|
|
||||||
public final net.corda.core.serialization.SerializationFactory getSerializationFactory()
|
public final net.corda.core.serialization.SerializationFactory getSerializationFactory()
|
||||||
public static final net.corda.testing.core.SerializationEnvironmentRule$Companion Companion
|
public static final net.corda.testing.core.SerializationEnvironmentRule$Companion Companion
|
||||||
##
|
##
|
||||||
|
9
.ci/ci-gradle-build-cache-init.sh
Executable file
9
.ci/ci-gradle-build-cache-init.sh
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
export GRADLE_BUILD_CACHE_URL="${GRADLE_BUILD_CACHE_URL:-http://localhost:5071/cache/}"
|
||||||
|
export USE_GRADLE_DAEMON="${USE_GRADLE_DAEMON:-false}"
|
||||||
|
export GRADLE_CACHE_DEBUG="${GRADLE_CACHE_DEBUG:-false}"
|
||||||
|
export PERFORM_GRADLE_SCAN="${PERFORM_GRADLE_SCAN:---scan}"
|
||||||
|
|
||||||
|
# cd %teamcity.build.checkoutDir%
|
||||||
|
echo "Using Gradle Build Cache: $GRADLE_BUILD_CACHE_URL"
|
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@ -235,4 +235,4 @@
|
|||||||
<component name="JavacSettings">
|
<component name="JavacSettings">
|
||||||
<option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" />
|
<option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
|
@ -84,6 +84,7 @@ see changes to this list.
|
|||||||
* Giulio Katis (Westpac)
|
* Giulio Katis (Westpac)
|
||||||
* Giuseppe Cardone (Intesa Sanpaolo)
|
* Giuseppe Cardone (Intesa Sanpaolo)
|
||||||
* Guy Hochstetler (R3)
|
* Guy Hochstetler (R3)
|
||||||
|
* Hristo Gatsinski (Industria)
|
||||||
* Ian Cusden (UBS)
|
* Ian Cusden (UBS)
|
||||||
* Ian Grigg (R3)
|
* Ian Grigg (R3)
|
||||||
* Igor Nitto (R3)
|
* Igor Nitto (R3)
|
||||||
@ -91,6 +92,7 @@ see changes to this list.
|
|||||||
* Ivan Schasny (R3)
|
* Ivan Schasny (R3)
|
||||||
* James Brown (R3)
|
* James Brown (R3)
|
||||||
* James Carlyle (R3)
|
* James Carlyle (R3)
|
||||||
|
* Janis Olekss (Accenture)
|
||||||
* Jared Harwayne-Gidansky (BNY Mellon)
|
* Jared Harwayne-Gidansky (BNY Mellon)
|
||||||
* Jayavaradhan Sambedu (Société Générale)
|
* Jayavaradhan Sambedu (Société Générale)
|
||||||
* Joel Dudley (R3)
|
* Joel Dudley (R3)
|
||||||
@ -116,7 +118,7 @@ see changes to this list.
|
|||||||
* Lars Stage Thomsen (Danske Bank)
|
* Lars Stage Thomsen (Danske Bank)
|
||||||
* Lee Braine (Barclays)
|
* Lee Braine (Barclays)
|
||||||
* Lucas Salmen (Itau)
|
* Lucas Salmen (Itau)
|
||||||
* Lulu Ren (S-Labs)
|
* Lulu Ren (Monad-Labs)
|
||||||
* Maksymilian Pawlak (R3)
|
* Maksymilian Pawlak (R3)
|
||||||
* Marek Scocovsky (ABSA)
|
* Marek Scocovsky (ABSA)
|
||||||
* marekdapps
|
* marekdapps
|
||||||
@ -137,6 +139,7 @@ see changes to this list.
|
|||||||
* Mike Hearn (R3)
|
* Mike Hearn (R3)
|
||||||
* Mike Ward (R3)
|
* Mike Ward (R3)
|
||||||
* Mike Reichelt (US Bank)
|
* Mike Reichelt (US Bank)
|
||||||
|
* Milen Dobrinov (Industria)
|
||||||
* Mohamed Amine LEGHERABA
|
* Mohamed Amine LEGHERABA
|
||||||
* Mustafa Ozturk (Natixis)
|
* Mustafa Ozturk (Natixis)
|
||||||
* Nick Skinner (Northern Trust)
|
* Nick Skinner (Northern Trust)
|
||||||
@ -145,6 +148,7 @@ see changes to this list.
|
|||||||
* Nuam Athaweth (MUFG)
|
* Nuam Athaweth (MUFG)
|
||||||
* Oscar Zibordi de Paiva (Scopus Soluções em TI)
|
* Oscar Zibordi de Paiva (Scopus Soluções em TI)
|
||||||
* OP Financial
|
* OP Financial
|
||||||
|
* Parnika Sharma (BCS Technology)
|
||||||
* Patrick Kuo (R3)
|
* Patrick Kuo (R3)
|
||||||
* Pekka Kaipio (OP Financial)
|
* Pekka Kaipio (OP Financial)
|
||||||
* Phillip Griffin
|
* Phillip Griffin
|
||||||
@ -176,6 +180,7 @@ see changes to this list.
|
|||||||
* Scott James
|
* Scott James
|
||||||
* Sean Zhang (Wells Fargo)
|
* Sean Zhang (Wells Fargo)
|
||||||
* Shams Asari (R3)
|
* Shams Asari (R3)
|
||||||
|
* Shivan Sawant (Persistent Systems Limited)
|
||||||
* Siddhartha Sengupta (Tradewind Markets)
|
* Siddhartha Sengupta (Tradewind Markets)
|
||||||
* Simon Taylor (Barclays)
|
* Simon Taylor (Barclays)
|
||||||
* Sofus Mortensen (Digital Asset Holdings)
|
* Sofus Mortensen (Digital Asset Holdings)
|
||||||
|
51
build.gradle
51
build.gradle
@ -17,9 +17,9 @@ buildscript {
|
|||||||
ext.quasar_group = 'co.paralleluniverse'
|
ext.quasar_group = 'co.paralleluniverse'
|
||||||
ext.quasar_version = '0.7.10'
|
ext.quasar_version = '0.7.10'
|
||||||
|
|
||||||
// gradle-capsule-plugin:1.0.2 contains capsule:1.0.1
|
// gradle-capsule-plugin:1.0.2 contains capsule:1.0.1 by default.
|
||||||
// TODO: Upgrade gradle-capsule-plugin to a version with capsule:1.0.3
|
// We must configure it manually to use the latest capsule version.
|
||||||
ext.capsule_version = '1.0.1'
|
ext.capsule_version = '1.0.3'
|
||||||
|
|
||||||
ext.asm_version = '5.0.4'
|
ext.asm_version = '5.0.4'
|
||||||
ext.artemis_version = '2.6.2'
|
ext.artemis_version = '2.6.2'
|
||||||
@ -32,6 +32,7 @@ buildscript {
|
|||||||
ext.bouncycastle_version = constants.getProperty("bouncycastleVersion")
|
ext.bouncycastle_version = constants.getProperty("bouncycastleVersion")
|
||||||
ext.guava_version = constants.getProperty("guavaVersion")
|
ext.guava_version = constants.getProperty("guavaVersion")
|
||||||
ext.caffeine_version = constants.getProperty("caffeineVersion")
|
ext.caffeine_version = constants.getProperty("caffeineVersion")
|
||||||
|
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.okhttp_version = '3.5.0'
|
ext.okhttp_version = '3.5.0'
|
||||||
@ -46,7 +47,7 @@ buildscript {
|
|||||||
ext.hibernate_version = '5.3.6.Final'
|
ext.hibernate_version = '5.3.6.Final'
|
||||||
ext.h2_version = '1.4.197' // Update docs if renamed or removed.
|
ext.h2_version = '1.4.197' // Update docs if renamed or removed.
|
||||||
ext.postgresql_version = '42.1.4'
|
ext.postgresql_version = '42.1.4'
|
||||||
ext.rxjava_version = '1.2.4'
|
ext.rxjava_version = '1.3.8'
|
||||||
ext.dokka_version = '0.9.17'
|
ext.dokka_version = '0.9.17'
|
||||||
ext.eddsa_version = '0.2.0'
|
ext.eddsa_version = '0.2.0'
|
||||||
ext.dependency_checker_version = '3.1.0'
|
ext.dependency_checker_version = '3.1.0'
|
||||||
@ -55,9 +56,8 @@ buildscript {
|
|||||||
ext.crash_version = 'cadb53544fbb3c0fb901445da614998a6a419488'
|
ext.crash_version = 'cadb53544fbb3c0fb901445da614998a6a419488'
|
||||||
ext.jsr305_version = constants.getProperty("jsr305Version")
|
ext.jsr305_version = constants.getProperty("jsr305Version")
|
||||||
ext.shiro_version = '1.4.0'
|
ext.shiro_version = '1.4.0'
|
||||||
ext.shadow_version = '2.0.4'
|
|
||||||
ext.artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion')
|
ext.artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion')
|
||||||
ext.liquibase_version = '3.6.2'
|
ext.liquibase_version = '3.5.5'
|
||||||
ext.artifactory_contextUrl = 'https://ci-artifactory.corda.r3cev.com/artifactory'
|
ext.artifactory_contextUrl = 'https://ci-artifactory.corda.r3cev.com/artifactory'
|
||||||
ext.snake_yaml_version = constants.getProperty('snakeYamlVersion')
|
ext.snake_yaml_version = constants.getProperty('snakeYamlVersion')
|
||||||
ext.docker_compose_rule_version = '0.33.0'
|
ext.docker_compose_rule_version = '0.33.0'
|
||||||
@ -78,6 +78,7 @@ buildscript {
|
|||||||
|
|
||||||
// Update 121 is required for ObjectInputFilter.
|
// Update 121 is required for ObjectInputFilter.
|
||||||
// Updates [131, 161] also have zip compression bugs on MacOS (High Sierra).
|
// Updates [131, 161] also have zip compression bugs on MacOS (High Sierra).
|
||||||
|
// when the java version in NodeStartup.hasMinimumJavaVersion() changes, so must this check
|
||||||
ext.java8_minUpdateVersion = '171'
|
ext.java8_minUpdateVersion = '171'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
@ -115,8 +116,12 @@ buildscript {
|
|||||||
plugins {
|
plugins {
|
||||||
// TODO The capsule plugin requires the newer DSL plugin block.It would be nice if we could unify all the plugins into one style,
|
// TODO The capsule plugin requires the newer DSL plugin block.It would be nice if we could unify all the plugins into one style,
|
||||||
// but the DSL has some restrictions e.g can't be used on the allprojects section. So we should revisit this if there are improvements in Gradle.
|
// but the DSL has some restrictions e.g can't be used on the allprojects section. So we should revisit this if there are improvements in Gradle.
|
||||||
// Version 1.0.2 of this plugin uses capsule:1.0.1
|
// Version 1.0.2 of this plugin uses capsule:1.0.1 by default.
|
||||||
id "us.kirchmeier.capsule" version "1.0.2"
|
id 'us.kirchmeier.capsule' version '1.0.2' apply false
|
||||||
|
|
||||||
|
// Add the shadow plugin to the plugins classpath for the entire project.
|
||||||
|
id 'com.github.johnrengelman.shadow' version '2.0.4' apply false
|
||||||
|
id "com.gradle.build-scan" version "1.16"
|
||||||
}
|
}
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
@ -195,11 +200,18 @@ allprojects {
|
|||||||
|
|
||||||
if (System.getProperty("test.maxParallelForks") != null) {
|
if (System.getProperty("test.maxParallelForks") != null) {
|
||||||
maxParallelForks = Integer.valueOf(System.getProperty("test.maxParallelForks"))
|
maxParallelForks = Integer.valueOf(System.getProperty("test.maxParallelForks"))
|
||||||
|
logger.debug("System property test.maxParallelForks found - setting max parallel forks to $maxParallelForks for $project")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (project.path.startsWith(':experimental') && System.getProperty("experimental.test.enable") == null) {
|
if (project.path.startsWith(':experimental') && System.getProperty("experimental.test.enable") == null) {
|
||||||
enabled = false
|
enabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Required to use Gradle build cache (until Gradle 5.0 is released with default value of "append" set to false)
|
||||||
|
// See https://github.com/gradle/gradle/issues/5269 and https://github.com/gradle/gradle/pull/6419
|
||||||
|
extensions.configure(TypeOf.typeOf(JacocoTaskExtension)) { ex ->
|
||||||
|
ex.append = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
group 'net.corda'
|
group 'net.corda'
|
||||||
@ -209,6 +221,7 @@ allprojects {
|
|||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven { url "$artifactory_contextUrl/corda-dependencies" }
|
||||||
maven { url 'https://jitpack.io' }
|
maven { url 'https://jitpack.io' }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,6 +248,8 @@ allprojects {
|
|||||||
// We want to use SLF4J's version of these bindings: jcl-over-slf4j
|
// We want to use SLF4J's version of these bindings: jcl-over-slf4j
|
||||||
// Remove any transitive dependency on Apache's version.
|
// Remove any transitive dependency on Apache's version.
|
||||||
exclude group: 'commons-logging', module: 'commons-logging'
|
exclude group: 'commons-logging', module: 'commons-logging'
|
||||||
|
// Remove any transitive dependency on Logback (e.g. Liquibase 3.6 introduces this dependency)
|
||||||
|
exclude group: 'ch.qos.logback'
|
||||||
|
|
||||||
// Netty-All is an uber-jar which contains every Netty module.
|
// Netty-All is an uber-jar which contains every Netty module.
|
||||||
// Exclude it to force us to use the individual Netty modules instead.
|
// Exclude it to force us to use the individual Netty modules instead.
|
||||||
@ -255,12 +270,6 @@ allprojects {
|
|||||||
if (!JavaVersion.current().java8Compatible)
|
if (!JavaVersion.current().java8Compatible)
|
||||||
throw new GradleException("Corda requires Java 8, please upgrade to at least 1.8.0_$java8_minUpdateVersion")
|
throw new GradleException("Corda requires Java 8, please upgrade to at least 1.8.0_$java8_minUpdateVersion")
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenLocal()
|
|
||||||
mavenCentral()
|
|
||||||
jcenter()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required for building out the fat JAR.
|
// Required for building out the fat JAR.
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':node')
|
compile project(':node')
|
||||||
@ -333,6 +342,8 @@ bintrayConfig {
|
|||||||
'corda-rpc',
|
'corda-rpc',
|
||||||
'corda-core',
|
'corda-core',
|
||||||
'corda-core-deterministic',
|
'corda-core-deterministic',
|
||||||
|
'corda-deterministic-verifier',
|
||||||
|
'corda-djvm',
|
||||||
'corda',
|
'corda',
|
||||||
'corda-finance',
|
'corda-finance',
|
||||||
'corda-node',
|
'corda-node',
|
||||||
@ -350,7 +361,8 @@ bintrayConfig {
|
|||||||
'corda-serialization-deterministic',
|
'corda-serialization-deterministic',
|
||||||
'corda-tools-blob-inspector',
|
'corda-tools-blob-inspector',
|
||||||
'corda-tools-explorer',
|
'corda-tools-explorer',
|
||||||
'corda-tools-network-bootstrapper'
|
'corda-tools-network-bootstrapper',
|
||||||
|
'corda-tools-cliutils'
|
||||||
]
|
]
|
||||||
license {
|
license {
|
||||||
name = 'Apache-2.0'
|
name = 'Apache-2.0'
|
||||||
@ -382,7 +394,7 @@ artifactory {
|
|||||||
contextUrl = artifactory_contextUrl
|
contextUrl = artifactory_contextUrl
|
||||||
repository {
|
repository {
|
||||||
repoKey = 'corda-dev'
|
repoKey = 'corda-dev'
|
||||||
username = 'teamcity'
|
username = System.getenv('CORDA_ARTIFACTORY_USERNAME')
|
||||||
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD')
|
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,6 +443,11 @@ if (file('corda-docs-only-build').exists() || (System.getenv('CORDA_DOCS_ONLY_BU
|
|||||||
}
|
}
|
||||||
|
|
||||||
wrapper {
|
wrapper {
|
||||||
gradleVersion = "4.8.1"
|
gradleVersion = "4.10.1"
|
||||||
distributionType = Wrapper.DistributionType.ALL
|
distributionType = Wrapper.DistributionType.ALL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildScan {
|
||||||
|
termsOfServiceUrl = 'https://gradle.com/terms-of-service'
|
||||||
|
termsOfServiceAgree = 'yes'
|
||||||
|
}
|
15
buildCacheSettings.gradle
Normal file
15
buildCacheSettings.gradle
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// Gradle Build Cache configuration recommendation: https://docs.gradle.org/current/userguide/build_cache.html
|
||||||
|
ext {
|
||||||
|
isCiServer = System.getenv().containsKey("CORDA_CI")
|
||||||
|
gradleBuildCacheURL = System.getenv().containsKey("GRADLE_BUILD_CACHE_URL") ? System.getenv().get("GRADLE_BUILD_CACHE_URL") : 'http://localhost:5071/cache/'
|
||||||
|
}
|
||||||
|
|
||||||
|
buildCache {
|
||||||
|
local {
|
||||||
|
enabled = !isCiServer
|
||||||
|
}
|
||||||
|
remote(HttpBuildCache) {
|
||||||
|
url = gradleBuildCacheURL
|
||||||
|
push = isCiServer
|
||||||
|
}
|
||||||
|
}
|
@ -1,2 +1,4 @@
|
|||||||
rootProject.name = 'buildSrc'
|
rootProject.name = 'buildSrc'
|
||||||
include 'canonicalizer'
|
include 'canonicalizer'
|
||||||
|
|
||||||
|
apply from: '../buildCacheSettings.gradle'
|
@ -197,7 +197,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
|
|||||||
fun DigitalSignatureWithCert() {
|
fun DigitalSignatureWithCert() {
|
||||||
val digitalSignature = DigitalSignatureWithCert(MINI_CORP.identity.certificate, secureRandomBytes(128))
|
val digitalSignature = DigitalSignatureWithCert(MINI_CORP.identity.certificate, secureRandomBytes(128))
|
||||||
val json = mapper.valueToTree<ObjectNode>(digitalSignature)
|
val json = mapper.valueToTree<ObjectNode>(digitalSignature)
|
||||||
val (by, bytes) = json.assertHasOnlyFields("by", "bytes")
|
val (by, bytes) = json.assertHasOnlyFields("by", "bytes", "parentCertsChain")
|
||||||
assertThat(by.valueAs<X509Certificate>(mapper)).isEqualTo(MINI_CORP.identity.certificate)
|
assertThat(by.valueAs<X509Certificate>(mapper)).isEqualTo(MINI_CORP.identity.certificate)
|
||||||
assertThat(bytes.binaryValue()).isEqualTo(digitalSignature.bytes)
|
assertThat(bytes.binaryValue()).isEqualTo(digitalSignature.bytes)
|
||||||
assertThat(mapper.convertValue<DigitalSignatureWithCert>(json)).isEqualTo(digitalSignature)
|
assertThat(mapper.convertValue<DigitalSignatureWithCert>(json)).isEqualTo(digitalSignature)
|
||||||
@ -610,7 +610,8 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
|
|||||||
assertThat(json["serialNumber"].bigIntegerValue()).isEqualTo(cert.serialNumber)
|
assertThat(json["serialNumber"].bigIntegerValue()).isEqualTo(cert.serialNumber)
|
||||||
assertThat(json["issuer"].valueAs<X500Principal>(mapper)).isEqualTo(cert.issuerX500Principal)
|
assertThat(json["issuer"].valueAs<X500Principal>(mapper)).isEqualTo(cert.issuerX500Principal)
|
||||||
assertThat(json["subject"].valueAs<X500Principal>(mapper)).isEqualTo(cert.subjectX500Principal)
|
assertThat(json["subject"].valueAs<X500Principal>(mapper)).isEqualTo(cert.subjectX500Principal)
|
||||||
assertThat(json["publicKey"].valueAs<PublicKey>(mapper)).isEqualTo(cert.publicKey)
|
// cert.publicKey should be converted to a supported format (this is required because [Certificate] returns keys as SUN EC keys, not BC).
|
||||||
|
assertThat(json["publicKey"].valueAs<PublicKey>(mapper)).isEqualTo(Crypto.toSupportedPublicKey(cert.publicKey))
|
||||||
assertThat(json["notAfter"].valueAs<Date>(mapper)).isEqualTo(cert.notAfter)
|
assertThat(json["notAfter"].valueAs<Date>(mapper)).isEqualTo(cert.notAfter)
|
||||||
assertThat(json["notBefore"].valueAs<Date>(mapper)).isEqualTo(cert.notBefore)
|
assertThat(json["notBefore"].valueAs<Date>(mapper)).isEqualTo(cert.notBefore)
|
||||||
assertThat(json["encoded"].binaryValue()).isEqualTo(cert.encoded)
|
assertThat(json["encoded"].binaryValue()).isEqualTo(cert.encoded)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.corda.client.jfx.model
|
package net.corda.client.jfx.model
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.CacheLoader
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
import javafx.beans.value.ObservableValue
|
import javafx.beans.value.ObservableValue
|
||||||
import javafx.collections.FXCollections
|
import javafx.collections.FXCollections
|
||||||
@ -31,7 +32,7 @@ class NetworkIdentityModel {
|
|||||||
private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable)
|
private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable)
|
||||||
|
|
||||||
private val identityCache = Caffeine.newBuilder()
|
private val identityCache = Caffeine.newBuilder()
|
||||||
.buildNamed<PublicKey, ObservableValue<NodeInfo?>>("NetworkIdentityModel_identity", { publicKey ->
|
.buildNamed<PublicKey, ObservableValue<NodeInfo?>>("NetworkIdentityModel_identity", CacheLoader { publicKey: PublicKey ->
|
||||||
publicKey.let { rpcProxy.map { it?.cordaRPCOps?.nodeInfoFromParty(AnonymousParty(publicKey)) } }
|
publicKey.let { rpcProxy.map { it?.cordaRPCOps?.nodeInfoFromParty(AnonymousParty(publicKey)) } }
|
||||||
})
|
})
|
||||||
val notaries = ChosenList(rpcProxy.map { FXCollections.observableList(it?.cordaRPCOps?.notaryIdentities() ?: emptyList()) }, "notaries")
|
val notaries = ChosenList(rpcProxy.map { FXCollections.observableList(it?.cordaRPCOps?.notaryIdentities() ?: emptyList()) }, "notaries")
|
||||||
|
@ -55,7 +55,7 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
node = startNode(ALICE_NAME, 1, singletonList(rpcUser));
|
node = startNode(ALICE_NAME, 1000, singletonList(rpcUser));
|
||||||
client = new CordaRPCClient(requireNonNull(node.getNode().getConfiguration().getRpcOptions().getAddress()));
|
client = new CordaRPCClient(requireNonNull(node.getNode().getConfiguration().getRpcOptions().getAddress()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ class RPCStabilityTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object DummyOps : RPCOps {
|
object DummyOps : RPCOps {
|
||||||
override val protocolVersion = 0
|
override val protocolVersion = 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun waitUntilNumberOfThreadsStable(executorService: ScheduledExecutorService): Map<Thread, List<StackTraceElement>> {
|
private fun waitUntilNumberOfThreadsStable(executorService: ScheduledExecutorService): Map<Thread, List<StackTraceElement>> {
|
||||||
@ -107,7 +107,7 @@ class RPCStabilityTests {
|
|||||||
Try.on {
|
Try.on {
|
||||||
startRpcClient<RPCOps>(
|
startRpcClient<RPCOps>(
|
||||||
server.get().broker.hostAndPort!!,
|
server.get().broker.hostAndPort!!,
|
||||||
configuration = CordaRPCClientConfiguration.DEFAULT.copy(minimumServerProtocolVersion = 1)
|
configuration = CordaRPCClientConfiguration.DEFAULT.copy(minimumServerProtocolVersion = 1000)
|
||||||
).get()
|
).get()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -203,7 +203,7 @@ class RPCStabilityTests {
|
|||||||
rpcDriver {
|
rpcDriver {
|
||||||
val leakObservableOpsImpl = object : LeakObservableOps {
|
val leakObservableOpsImpl = object : LeakObservableOps {
|
||||||
val leakedUnsubscribedCount = AtomicInteger(0)
|
val leakedUnsubscribedCount = AtomicInteger(0)
|
||||||
override val protocolVersion = 0
|
override val protocolVersion = 1000
|
||||||
override fun leakObservable(): Observable<Nothing> {
|
override fun leakObservable(): Observable<Nothing> {
|
||||||
return PublishSubject.create<Nothing>().doOnUnsubscribe {
|
return PublishSubject.create<Nothing>().doOnUnsubscribe {
|
||||||
leakedUnsubscribedCount.incrementAndGet()
|
leakedUnsubscribedCount.incrementAndGet()
|
||||||
@ -234,7 +234,7 @@ class RPCStabilityTests {
|
|||||||
fun `client reconnects to rebooted server`() {
|
fun `client reconnects to rebooted server`() {
|
||||||
rpcDriver {
|
rpcDriver {
|
||||||
val ops = object : ReconnectOps {
|
val ops = object : ReconnectOps {
|
||||||
override val protocolVersion = 0
|
override val protocolVersion = 1000
|
||||||
override fun ping() = "pong"
|
override fun ping() = "pong"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,7 +259,7 @@ class RPCStabilityTests {
|
|||||||
fun `connection failover fails, rpc calls throw`() {
|
fun `connection failover fails, rpc calls throw`() {
|
||||||
rpcDriver {
|
rpcDriver {
|
||||||
val ops = object : ReconnectOps {
|
val ops = object : ReconnectOps {
|
||||||
override val protocolVersion = 0
|
override val protocolVersion = 1000
|
||||||
override fun ping() = "pong"
|
override fun ping() = "pong"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,7 +290,7 @@ class RPCStabilityTests {
|
|||||||
fun `observables error when connection breaks`() {
|
fun `observables error when connection breaks`() {
|
||||||
rpcDriver {
|
rpcDriver {
|
||||||
val ops = object : NoOps {
|
val ops = object : NoOps {
|
||||||
override val protocolVersion = 0
|
override val protocolVersion = 1000
|
||||||
override fun subscribe(): Observable<Nothing> {
|
override fun subscribe(): Observable<Nothing> {
|
||||||
return PublishSubject.create<Nothing>()
|
return PublishSubject.create<Nothing>()
|
||||||
}
|
}
|
||||||
@ -350,7 +350,7 @@ class RPCStabilityTests {
|
|||||||
fun `client connects to first available server`() {
|
fun `client connects to first available server`() {
|
||||||
rpcDriver {
|
rpcDriver {
|
||||||
val ops = object : ServerOps {
|
val ops = object : ServerOps {
|
||||||
override val protocolVersion = 0
|
override val protocolVersion = 1000
|
||||||
override fun serverId() = "server"
|
override fun serverId() = "server"
|
||||||
}
|
}
|
||||||
val serverFollower = shutdownManager.follower()
|
val serverFollower = shutdownManager.follower()
|
||||||
@ -371,15 +371,15 @@ class RPCStabilityTests {
|
|||||||
fun `3 server failover`() {
|
fun `3 server failover`() {
|
||||||
rpcDriver {
|
rpcDriver {
|
||||||
val ops1 = object : ServerOps {
|
val ops1 = object : ServerOps {
|
||||||
override val protocolVersion = 0
|
override val protocolVersion = 1000
|
||||||
override fun serverId() = "server1"
|
override fun serverId() = "server1"
|
||||||
}
|
}
|
||||||
val ops2 = object : ServerOps {
|
val ops2 = object : ServerOps {
|
||||||
override val protocolVersion = 0
|
override val protocolVersion = 1000
|
||||||
override fun serverId() = "server2"
|
override fun serverId() = "server2"
|
||||||
}
|
}
|
||||||
val ops3 = object : ServerOps {
|
val ops3 = object : ServerOps {
|
||||||
override val protocolVersion = 0
|
override val protocolVersion = 1000
|
||||||
override fun serverId() = "server3"
|
override fun serverId() = "server3"
|
||||||
}
|
}
|
||||||
val serverFollower1 = shutdownManager.follower()
|
val serverFollower1 = shutdownManager.follower()
|
||||||
@ -443,7 +443,7 @@ class RPCStabilityTests {
|
|||||||
fun `server cleans up queues after disconnected clients`() {
|
fun `server cleans up queues after disconnected clients`() {
|
||||||
rpcDriver {
|
rpcDriver {
|
||||||
val trackSubscriberOpsImpl = object : TrackSubscriberOps {
|
val trackSubscriberOpsImpl = object : TrackSubscriberOps {
|
||||||
override val protocolVersion = 0
|
override val protocolVersion = 1000
|
||||||
val subscriberCount = AtomicInteger(0)
|
val subscriberCount = AtomicInteger(0)
|
||||||
val trackSubscriberCountObservable = UnicastSubject.create<Unit>().share().
|
val trackSubscriberCountObservable = UnicastSubject.create<Unit>().share().
|
||||||
doOnSubscribe { subscriberCount.incrementAndGet() }.
|
doOnSubscribe { subscriberCount.incrementAndGet() }.
|
||||||
@ -486,7 +486,7 @@ class RPCStabilityTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SlowConsumerRPCOpsImpl : SlowConsumerRPCOps {
|
class SlowConsumerRPCOpsImpl : SlowConsumerRPCOps {
|
||||||
override val protocolVersion = 0
|
override val protocolVersion = 1000
|
||||||
|
|
||||||
override fun streamAtInterval(interval: Duration, size: Int): Observable<ByteArray> {
|
override fun streamAtInterval(interval: Duration, size: Int): Observable<ByteArray> {
|
||||||
val chunk = ByteArray(size)
|
val chunk = ByteArray(size)
|
||||||
@ -587,7 +587,7 @@ class RPCStabilityTests {
|
|||||||
val request = RPCApi.ClientToServer.fromClientMessage(it)
|
val request = RPCApi.ClientToServer.fromClientMessage(it)
|
||||||
when (request) {
|
when (request) {
|
||||||
is RPCApi.ClientToServer.RpcRequest -> {
|
is RPCApi.ClientToServer.RpcRequest -> {
|
||||||
val reply = RPCApi.ServerToClient.RpcReply(request.replyId, Try.Success(0), "server")
|
val reply = RPCApi.ServerToClient.RpcReply(request.replyId, Try.Success(1000), "server")
|
||||||
val message = session.createMessage(false)
|
val message = session.createMessage(false)
|
||||||
reply.writeToClientMessage(SerializationDefaults.RPC_SERVER_CONTEXT, message)
|
reply.writeToClientMessage(SerializationDefaults.RPC_SERVER_CONTEXT, message)
|
||||||
message.putLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME, dedupeId.getAndIncrement())
|
message.putLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME, dedupeId.getAndIncrement())
|
||||||
|
@ -4,15 +4,16 @@ import net.corda.client.rpc.internal.RPCClient
|
|||||||
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
|
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
|
||||||
import net.corda.core.context.Actor
|
import net.corda.core.context.Actor
|
||||||
import net.corda.core.context.Trace
|
import net.corda.core.context.Trace
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.serialization.internal.effectiveSerializationEnv
|
import net.corda.core.serialization.internal.effectiveSerializationEnv
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcConnectorTcpTransport
|
|
||||||
import net.corda.core.messaging.ClientRpcSslOptions
|
import net.corda.core.messaging.ClientRpcSslOptions
|
||||||
import net.corda.core.utilities.days
|
import net.corda.core.utilities.days
|
||||||
import net.corda.core.utilities.minutes
|
import net.corda.core.utilities.minutes
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.internal.ArtemisTcpTransport.Companion.rpcConnectorTcpTransport
|
||||||
|
import net.corda.nodeapi.internal.PLATFORM_VERSION
|
||||||
import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
|
import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
|
||||||
@ -29,65 +30,76 @@ class CordaRPCConnection internal constructor(connection: RPCConnection<CordaRPC
|
|||||||
open class CordaRPCClientConfiguration @JvmOverloads constructor(
|
open class CordaRPCClientConfiguration @JvmOverloads constructor(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum retry interval.
|
* The maximum retry interval for re-connections. The client will retry connections if the host is lost with
|
||||||
|
* ever increasing spacing until the max is reached. The default is 3 minutes.
|
||||||
*/
|
*/
|
||||||
open val connectionMaxRetryInterval: Duration = 3.minutes,
|
open val connectionMaxRetryInterval: Duration = 3.minutes,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The minimum protocol version required from the server.
|
* The minimum protocol version required from the server. This is equivalent to the node's platform version
|
||||||
|
* number. If this minimum version is not met, an exception will be thrown at startup. If you use features
|
||||||
|
* introduced in a later version, you can bump this to match the platform version you need and get an early
|
||||||
|
* check that runs before you do anything.
|
||||||
|
*
|
||||||
|
* If you leave it at the default then things will work but attempting to use an RPC added in a version later
|
||||||
|
* than the server supports will throw [UnsupportedOperationException].
|
||||||
|
*
|
||||||
|
* The default value is whatever version of Corda this RPC library was shipped as a part of. Therefore if you
|
||||||
|
* use the RPC library from Corda 4, it will by default only connect to a node of version 4 or above.
|
||||||
*/
|
*/
|
||||||
open val minimumServerProtocolVersion: Int = 0,
|
open val minimumServerProtocolVersion: Int = PLATFORM_VERSION,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If set to true the client will track RPC call sites. If an error occurs subsequently during the RPC or in a
|
* If set to true the client will track RPC call sites (default is false). If an error occurs subsequently
|
||||||
* returned Observable stream the stack trace of the originating RPC will be shown as well. Note that
|
* during the RPC or in a returned Observable stream the stack trace of the originating RPC will be shown as
|
||||||
* constructing call stacks is a moderately expensive operation.
|
* well. Note that constructing call stacks is a moderately expensive operation.
|
||||||
*/
|
*/
|
||||||
open val trackRpcCallSites: Boolean = false,
|
open val trackRpcCallSites: Boolean = java.lang.Boolean.getBoolean("net.corda.client.rpc.trackRpcCallSites"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The interval of unused observable reaping. Leaked Observables (unused ones) are detected using weak references
|
* The interval of unused observable reaping. Leaked Observables (unused ones) are detected using weak references
|
||||||
* and are cleaned up in batches in this interval. If set too large it will waste server side resources for this
|
* and are cleaned up in batches in this interval. If set too large it will waste server side resources for this
|
||||||
* duration. If set too low it wastes client side cycles.
|
* duration. If set too low it wastes client side cycles. The default is to check once per second.
|
||||||
*/
|
*/
|
||||||
open val reapInterval: Duration = 1.seconds,
|
open val reapInterval: Duration = 1.seconds,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of threads to use for observations (for executing [Observable.onNext]).
|
* The number of threads to use for observations for executing [Observable.onNext]. This only has any effect
|
||||||
|
* if [observableExecutor] is null (which is the default). The default is 4.
|
||||||
*/
|
*/
|
||||||
open val observationExecutorPoolSize: Int = 4,
|
open val observationExecutorPoolSize: Int = 4,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines the concurrency level of the Observable Cache. This is exposed because it implicitly determines
|
* This property is no longer used and has no effect.
|
||||||
* the limit on the number of leaked observables reaped because of garbage collection per reaping.
|
* @suppress
|
||||||
* See the implementation of [com.google.common.cache.LocalCache] for details.
|
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("This field is no longer used and has no effect.")
|
||||||
open val cacheConcurrencyLevel: Int = 1,
|
open val cacheConcurrencyLevel: Int = 1,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The retry interval of Artemis connections in milliseconds.
|
* The base retry interval for reconnection attempts. The default is 5 seconds.
|
||||||
*/
|
*/
|
||||||
open val connectionRetryInterval: Duration = 5.seconds,
|
open val connectionRetryInterval: Duration = 5.seconds,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The retry interval multiplier for exponential backoff.
|
* The retry interval multiplier for exponential backoff. The default is 1.5
|
||||||
*/
|
*/
|
||||||
open val connectionRetryIntervalMultiplier: Double = 1.5,
|
open val connectionRetryIntervalMultiplier: Double = 1.5,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum reconnect attempts on failover>
|
* Maximum reconnect attempts on failover or disconnection. The default is -1 which means unlimited.
|
||||||
*/
|
*/
|
||||||
open val maxReconnectAttempts: Int = unlimitedReconnectAttempts,
|
open val maxReconnectAttempts: Int = unlimitedReconnectAttempts,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum file size, in bytes.
|
* Maximum size of RPC responses, in bytes. Default is 10mb.
|
||||||
*/
|
*/
|
||||||
open val maxFileSize: Int = 10485760,
|
open val maxFileSize: Int = 10485760,
|
||||||
// 10 MiB maximum allowed file size for attachments, including message headers.
|
// 10 MiB maximum allowed file size for attachments, including message headers.
|
||||||
// TODO: acquire this value from Network Map when supported.
|
// TODO: acquire this value from Network Map when supported.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The cache expiry of a deduplication watermark per client.
|
* The cache expiry of a deduplication watermark per client. Default is 1 day.
|
||||||
*/
|
*/
|
||||||
open val deduplicationCacheExpiry: Duration = 1.days
|
open val deduplicationCacheExpiry: Duration = 1.days
|
||||||
|
|
||||||
@ -97,6 +109,7 @@ open class CordaRPCClientConfiguration @JvmOverloads constructor(
|
|||||||
|
|
||||||
private const val unlimitedReconnectAttempts = -1
|
private const val unlimitedReconnectAttempts = -1
|
||||||
|
|
||||||
|
/** Provides an instance of this class with the parameters set to our recommended defaults. */
|
||||||
@JvmField
|
@JvmField
|
||||||
val DEFAULT: CordaRPCClientConfiguration = CordaRPCClientConfiguration()
|
val DEFAULT: CordaRPCClientConfiguration = CordaRPCClientConfiguration()
|
||||||
|
|
||||||
@ -104,7 +117,10 @@ open class CordaRPCClientConfiguration @JvmOverloads constructor(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new copy of a configuration object with zero or more parameters modified.
|
* Create a new copy of a configuration object with zero or more parameters modified.
|
||||||
|
*
|
||||||
|
* @suppress
|
||||||
*/
|
*/
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun copy(
|
fun copy(
|
||||||
connectionMaxRetryInterval: Duration = this.connectionMaxRetryInterval,
|
connectionMaxRetryInterval: Duration = this.connectionMaxRetryInterval,
|
||||||
@ -169,6 +185,7 @@ open class CordaRPCClientConfiguration @JvmOverloads constructor(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "CordaRPCClientConfiguration(" +
|
return "CordaRPCClientConfiguration(" +
|
||||||
"connectionMaxRetryInterval=$connectionMaxRetryInterval, " +
|
"connectionMaxRetryInterval=$connectionMaxRetryInterval, " +
|
||||||
@ -180,7 +197,8 @@ open class CordaRPCClientConfiguration @JvmOverloads constructor(
|
|||||||
"deduplicationCacheExpiry=$deduplicationCacheExpiry)"
|
"deduplicationCacheExpiry=$deduplicationCacheExpiry)"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Left is for backwards compatibility with version 3.1
|
// Left in for backwards compatibility with version 3.1
|
||||||
|
@Deprecated("Binary compatibility stub")
|
||||||
operator fun component1() = connectionMaxRetryInterval
|
operator fun component1() = connectionMaxRetryInterval
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -226,10 +244,8 @@ class CordaRPCClient private constructor(
|
|||||||
private val hostAndPort: NetworkHostAndPort,
|
private val hostAndPort: NetworkHostAndPort,
|
||||||
private val configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
private val configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
||||||
private val sslConfiguration: ClientRpcSslOptions? = null,
|
private val sslConfiguration: ClientRpcSslOptions? = null,
|
||||||
private val nodeSslConfiguration: SSLConfiguration? = null,
|
|
||||||
private val classLoader: ClassLoader? = null,
|
private val classLoader: ClassLoader? = null,
|
||||||
private val haAddressPool: List<NetworkHostAndPort> = emptyList(),
|
private val haAddressPool: List<NetworkHostAndPort> = emptyList()
|
||||||
private val internalConnection: Boolean = false
|
|
||||||
) {
|
) {
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(hostAndPort: NetworkHostAndPort,
|
constructor(hostAndPort: NetworkHostAndPort,
|
||||||
@ -243,7 +259,7 @@ class CordaRPCClient private constructor(
|
|||||||
* @param configuration An optional configuration used to tweak client behaviour.
|
* @param configuration An optional configuration used to tweak client behaviour.
|
||||||
*/
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(haAddressPool: List<NetworkHostAndPort>, configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT) : this(haAddressPool.first(), configuration, null, null, null, haAddressPool)
|
constructor(haAddressPool: List<NetworkHostAndPort>, configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT) : this(haAddressPool.first(), configuration, null, null, haAddressPool)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun createWithSsl(
|
fun createWithSsl(
|
||||||
@ -268,16 +284,7 @@ class CordaRPCClient private constructor(
|
|||||||
sslConfiguration: ClientRpcSslOptions? = null,
|
sslConfiguration: ClientRpcSslOptions? = null,
|
||||||
classLoader: ClassLoader? = null
|
classLoader: ClassLoader? = null
|
||||||
): CordaRPCClient {
|
): CordaRPCClient {
|
||||||
return CordaRPCClient(hostAndPort, configuration, sslConfiguration, null, classLoader)
|
return CordaRPCClient(hostAndPort, configuration, sslConfiguration, classLoader)
|
||||||
}
|
|
||||||
|
|
||||||
internal fun createWithInternalSslAndClassLoader(
|
|
||||||
hostAndPort: NetworkHostAndPort,
|
|
||||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
|
||||||
sslConfiguration: SSLConfiguration?,
|
|
||||||
classLoader: ClassLoader? = null
|
|
||||||
): CordaRPCClient {
|
|
||||||
return CordaRPCClient(hostAndPort, configuration, null, sslConfiguration, classLoader, internalConnection = true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -295,9 +302,6 @@ class CordaRPCClient private constructor(
|
|||||||
|
|
||||||
private fun getRpcClient(): RPCClient<CordaRPCOps> {
|
private fun getRpcClient(): RPCClient<CordaRPCOps> {
|
||||||
return when {
|
return when {
|
||||||
// Node->RPC broker, mutually authenticated SSL. This is used when connecting the integrated shell
|
|
||||||
internalConnection == true -> RPCClient(hostAndPort, nodeSslConfiguration!!)
|
|
||||||
|
|
||||||
// Client->RPC broker
|
// Client->RPC broker
|
||||||
haAddressPool.isEmpty() -> RPCClient(
|
haAddressPool.isEmpty() -> RPCClient(
|
||||||
rpcConnectorTcpTransport(hostAndPort, config = sslConfiguration),
|
rpcConnectorTcpTransport(hostAndPort, config = sslConfiguration),
|
||||||
@ -326,6 +330,21 @@ class CordaRPCClient private constructor(
|
|||||||
return start(username, password, null, null)
|
return start(username, password, null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs in to the target server and returns an active connection. The returned connection is a [java.io.Closeable]
|
||||||
|
* and can be used with a try-with-resources statement. If you don't use that, you should use the
|
||||||
|
* [RPCConnection.notifyServerAndClose] or [RPCConnection.forceClose] methods to dispose of the connection object
|
||||||
|
* when done.
|
||||||
|
*
|
||||||
|
* @param username The username to authenticate with.
|
||||||
|
* @param password The password to authenticate with.
|
||||||
|
* @param targetLegalIdentity in case of multi-identity RPC endpoint specific legal identity to which the calls must be addressed.
|
||||||
|
* @throws RPCException if the server version is too low or if the server isn't reachable within a reasonable timeout.
|
||||||
|
*/
|
||||||
|
fun start(username: String, password: String, targetLegalIdentity: CordaX500Name): CordaRPCConnection {
|
||||||
|
return start(username, password, null, null, targetLegalIdentity)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs in to the target server and returns an active connection. The returned connection is a [java.io.Closeable]
|
* Logs in to the target server and returns an active connection. The returned connection is a [java.io.Closeable]
|
||||||
* and can be used with a try-with-resources statement. If you don't use that, you should use the
|
* and can be used with a try-with-resources statement. If you don't use that, you should use the
|
||||||
@ -335,10 +354,28 @@ class CordaRPCClient private constructor(
|
|||||||
* @param username The username to authenticate with.
|
* @param username The username to authenticate with.
|
||||||
* @param password The password to authenticate with.
|
* @param password The password to authenticate with.
|
||||||
* @param externalTrace external [Trace] for correlation.
|
* @param externalTrace external [Trace] for correlation.
|
||||||
|
* @param impersonatedActor the actor on behalf of which all the invocations will be made.
|
||||||
* @throws RPCException if the server version is too low or if the server isn't reachable within a reasonable timeout.
|
* @throws RPCException if the server version is too low or if the server isn't reachable within a reasonable timeout.
|
||||||
*/
|
*/
|
||||||
fun start(username: String, password: String, externalTrace: Trace?, impersonatedActor: Actor?): CordaRPCConnection {
|
fun start(username: String, password: String, externalTrace: Trace?, impersonatedActor: Actor?): CordaRPCConnection {
|
||||||
return CordaRPCConnection(getRpcClient().start(CordaRPCOps::class.java, username, password, externalTrace, impersonatedActor))
|
return start(username, password, externalTrace, impersonatedActor, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs in to the target server and returns an active connection. The returned connection is a [java.io.Closeable]
|
||||||
|
* and can be used with a try-with-resources statement. If you don't use that, you should use the
|
||||||
|
* [RPCConnection.notifyServerAndClose] or [RPCConnection.forceClose] methods to dispose of the connection object
|
||||||
|
* when done.
|
||||||
|
*
|
||||||
|
* @param username The username to authenticate with.
|
||||||
|
* @param password The password to authenticate with.
|
||||||
|
* @param externalTrace external [Trace] for correlation.
|
||||||
|
* @param impersonatedActor the actor on behalf of which all the invocations will be made.
|
||||||
|
* @param targetLegalIdentity in case of multi-identity RPC endpoint specific legal identity to which the calls must be addressed.
|
||||||
|
* @throws RPCException if the server version is too low or if the server isn't reachable within a reasonable timeout.
|
||||||
|
*/
|
||||||
|
fun start(username: String, password: String, externalTrace: Trace?, impersonatedActor: Actor?, targetLegalIdentity: CordaX500Name?): CordaRPCConnection {
|
||||||
|
return CordaRPCConnection(getRpcClient().start(CordaRPCOps::class.java, username, password, externalTrace, impersonatedActor, targetLegalIdentity))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,9 +3,13 @@ package net.corda.client.rpc
|
|||||||
import net.corda.core.CordaRuntimeException
|
import net.corda.core.CordaRuntimeException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown to indicate a fatal error in the RPC system itself, as opposed to an error generated by the invoked
|
* Thrown to indicate a fatal error in the RPC system itself, as opposed to an error generated by the invoked method.
|
||||||
* method.
|
|
||||||
*/
|
*/
|
||||||
open class RPCException(message: String?, cause: Throwable?) : CordaRuntimeException(message, cause) {
|
open class RPCException(message: String?, cause: Throwable?) : CordaRuntimeException(message, cause) {
|
||||||
constructor(msg: String) : this(msg, null)
|
constructor(msg: String) : this(msg, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals that the underlying [RPCConnection] dropped.
|
||||||
|
*/
|
||||||
|
open class ConnectionFailureException(cause: Throwable? = null) : RPCException("Connection failure detected.", cause)
|
@ -2,12 +2,8 @@ package net.corda.client.rpc.internal
|
|||||||
|
|
||||||
import net.corda.client.rpc.CordaRPCClient
|
import net.corda.client.rpc.CordaRPCClient
|
||||||
import net.corda.client.rpc.CordaRPCClientConfiguration
|
import net.corda.client.rpc.CordaRPCClientConfiguration
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
|
||||||
import net.corda.core.messaging.pendingFlowsCount
|
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.messaging.ClientRpcSslOptions
|
import net.corda.core.messaging.ClientRpcSslOptions
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
|
||||||
import rx.Observable
|
|
||||||
|
|
||||||
/** Utility which exposes the internal Corda RPC constructor to other internal Corda components */
|
/** Utility which exposes the internal Corda RPC constructor to other internal Corda components */
|
||||||
fun createCordaRPCClientWithSslAndClassLoader(
|
fun createCordaRPCClientWithSslAndClassLoader(
|
||||||
@ -15,21 +11,4 @@ fun createCordaRPCClientWithSslAndClassLoader(
|
|||||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
||||||
sslConfiguration: ClientRpcSslOptions? = null,
|
sslConfiguration: ClientRpcSslOptions? = null,
|
||||||
classLoader: ClassLoader? = null
|
classLoader: ClassLoader? = null
|
||||||
) = CordaRPCClient.createWithSslAndClassLoader(hostAndPort, configuration, sslConfiguration, classLoader)
|
) = CordaRPCClient.createWithSslAndClassLoader(hostAndPort, configuration, sslConfiguration, classLoader)
|
||||||
|
|
||||||
fun createCordaRPCClientWithInternalSslAndClassLoader(
|
|
||||||
hostAndPort: NetworkHostAndPort,
|
|
||||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
|
||||||
sslConfiguration: SSLConfiguration? = null,
|
|
||||||
classLoader: ClassLoader? = null
|
|
||||||
) = CordaRPCClient.createWithInternalSslAndClassLoader(hostAndPort, configuration, sslConfiguration, classLoader)
|
|
||||||
|
|
||||||
fun CordaRPCOps.drainAndShutdown(): Observable<Unit> {
|
|
||||||
|
|
||||||
setFlowsDrainingModeEnabled(true)
|
|
||||||
return pendingFlowsCount().updates
|
|
||||||
.doOnError { error ->
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
.doOnCompleted { shutdown() }.map { }
|
|
||||||
}
|
|
@ -6,6 +6,7 @@ import net.corda.client.rpc.RPCException
|
|||||||
import net.corda.core.context.Actor
|
import net.corda.core.context.Actor
|
||||||
import net.corda.core.context.Trace
|
import net.corda.core.context.Trace
|
||||||
import net.corda.core.crypto.random63BitValue
|
import net.corda.core.crypto.random63BitValue
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.internal.logElapsedTime
|
import net.corda.core.internal.logElapsedTime
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.messaging.ClientRpcSslOptions
|
import net.corda.core.messaging.ClientRpcSslOptions
|
||||||
@ -15,11 +16,11 @@ import net.corda.core.serialization.SerializationDefaults
|
|||||||
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.nodeapi.ArtemisTcpTransport.Companion.rpcConnectorTcpTransport
|
|
||||||
import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcConnectorTcpTransportsFromList
|
|
||||||
import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcInternalClientTcpTransport
|
|
||||||
import net.corda.nodeapi.RPCApi
|
import net.corda.nodeapi.RPCApi
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.internal.ArtemisTcpTransport.Companion.rpcConnectorTcpTransport
|
||||||
|
import net.corda.nodeapi.internal.ArtemisTcpTransport.Companion.rpcConnectorTcpTransportsFromList
|
||||||
|
import net.corda.nodeapi.internal.ArtemisTcpTransport.Companion.rpcInternalClientTcpTransport
|
||||||
|
import net.corda.nodeapi.internal.config.SslConfiguration
|
||||||
import org.apache.activemq.artemis.api.core.SimpleString
|
import org.apache.activemq.artemis.api.core.SimpleString
|
||||||
import org.apache.activemq.artemis.api.core.TransportConfiguration
|
import org.apache.activemq.artemis.api.core.TransportConfiguration
|
||||||
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
|
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
|
||||||
@ -43,7 +44,7 @@ class RPCClient<I : RPCOps>(
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
hostAndPort: NetworkHostAndPort,
|
hostAndPort: NetworkHostAndPort,
|
||||||
sslConfiguration: SSLConfiguration,
|
sslConfiguration: SslConfiguration,
|
||||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
||||||
serializationContext: SerializationContext = SerializationDefaults.RPC_CLIENT_CONTEXT
|
serializationContext: SerializationContext = SerializationDefaults.RPC_CLIENT_CONTEXT
|
||||||
) : this(rpcInternalClientTcpTransport(hostAndPort, sslConfiguration), configuration, serializationContext)
|
) : this(rpcInternalClientTcpTransport(hostAndPort, sslConfiguration), configuration, serializationContext)
|
||||||
@ -65,7 +66,8 @@ class RPCClient<I : RPCOps>(
|
|||||||
username: String,
|
username: String,
|
||||||
password: String,
|
password: String,
|
||||||
externalTrace: Trace? = null,
|
externalTrace: Trace? = null,
|
||||||
impersonatedActor: Actor? = null
|
impersonatedActor: Actor? = null,
|
||||||
|
targetLegalIdentity: CordaX500Name? = null
|
||||||
): RPCConnection<I> {
|
): RPCConnection<I> {
|
||||||
return log.logElapsedTime("Startup") {
|
return log.logElapsedTime("Startup") {
|
||||||
val clientAddress = SimpleString("${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$username.${random63BitValue()}")
|
val clientAddress = SimpleString("${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$username.${random63BitValue()}")
|
||||||
@ -85,7 +87,8 @@ class RPCClient<I : RPCOps>(
|
|||||||
isUseGlobalPools = nodeSerializationEnv != null
|
isUseGlobalPools = nodeSerializationEnv != null
|
||||||
}
|
}
|
||||||
val sessionId = Trace.SessionId.newInstance()
|
val sessionId = Trace.SessionId.newInstance()
|
||||||
val proxyHandler = RPCClientProxyHandler(rpcConfiguration, username, password, serverLocator, clientAddress, rpcOpsClass, serializationContext, sessionId, externalTrace, impersonatedActor)
|
val proxyHandler = RPCClientProxyHandler(rpcConfiguration, username, password, serverLocator, clientAddress,
|
||||||
|
rpcOpsClass, serializationContext, sessionId, externalTrace, impersonatedActor, targetLegalIdentity)
|
||||||
try {
|
try {
|
||||||
proxyHandler.start()
|
proxyHandler.start()
|
||||||
val ops: I = uncheckedCast(Proxy.newProxyInstance(rpcOpsClass.classLoader, arrayOf(rpcOpsClass), proxyHandler))
|
val ops: I = uncheckedCast(Proxy.newProxyInstance(rpcOpsClass.classLoader, arrayOf(rpcOpsClass), proxyHandler))
|
||||||
|
@ -7,6 +7,7 @@ import com.github.benmanes.caffeine.cache.RemovalCause
|
|||||||
import com.github.benmanes.caffeine.cache.RemovalListener
|
import com.github.benmanes.caffeine.cache.RemovalListener
|
||||||
import com.google.common.util.concurrent.SettableFuture
|
import com.google.common.util.concurrent.SettableFuture
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder
|
import com.google.common.util.concurrent.ThreadFactoryBuilder
|
||||||
|
import net.corda.client.rpc.ConnectionFailureException
|
||||||
import net.corda.client.rpc.CordaRPCClientConfiguration
|
import net.corda.client.rpc.CordaRPCClientConfiguration
|
||||||
import net.corda.client.rpc.RPCException
|
import net.corda.client.rpc.RPCException
|
||||||
import net.corda.client.rpc.RPCSinceVersion
|
import net.corda.client.rpc.RPCSinceVersion
|
||||||
@ -14,6 +15,7 @@ import net.corda.client.rpc.internal.serialization.amqp.RpcClientObservableDeSer
|
|||||||
import net.corda.core.context.Actor
|
import net.corda.core.context.Actor
|
||||||
import net.corda.core.context.Trace
|
import net.corda.core.context.Trace
|
||||||
import net.corda.core.context.Trace.InvocationId
|
import net.corda.core.context.Trace.InvocationId
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.internal.*
|
import net.corda.core.internal.*
|
||||||
import net.corda.core.messaging.RPCOps
|
import net.corda.core.messaging.RPCOps
|
||||||
import net.corda.core.serialization.SerializationContext
|
import net.corda.core.serialization.SerializationContext
|
||||||
@ -64,7 +66,7 @@ import kotlin.reflect.jvm.javaMethod
|
|||||||
* automatically signal the server. This is done using a cache that holds weak references to the [UnicastSubject]s.
|
* automatically signal the server. This is done using a cache that holds weak references to the [UnicastSubject]s.
|
||||||
* The cleanup happens in batches using a dedicated reaper, scheduled on [reaperExecutor].
|
* The cleanup happens in batches using a dedicated reaper, scheduled on [reaperExecutor].
|
||||||
*
|
*
|
||||||
* The client will attempt to failover in case the server become unreachable. Depending on the [ServerLocataor] instance
|
* The client will attempt to failover in case the server become unreachable. Depending on the [ServerLocator] instance
|
||||||
* passed in the constructor, failover is either handle at Artemis level or client level. If only one transport
|
* passed in the constructor, failover is either handle at Artemis level or client level. If only one transport
|
||||||
* was used to create the [ServerLocator], failover is handled by Artemis (retrying based on [CordaRPCClientConfiguration].
|
* was used to create the [ServerLocator], failover is handled by Artemis (retrying based on [CordaRPCClientConfiguration].
|
||||||
* If a list of transport configurations was used, failover is handled locally. Artemis is able to do it, however the
|
* If a list of transport configurations was used, failover is handled locally. Artemis is able to do it, however the
|
||||||
@ -80,7 +82,8 @@ class RPCClientProxyHandler(
|
|||||||
serializationContext: SerializationContext,
|
serializationContext: SerializationContext,
|
||||||
private val sessionId: Trace.SessionId,
|
private val sessionId: Trace.SessionId,
|
||||||
private val externalTrace: Trace?,
|
private val externalTrace: Trace?,
|
||||||
private val impersonatedActor: Actor?
|
private val impersonatedActor: Actor?,
|
||||||
|
private val targetLegalIdentity: CordaX500Name?
|
||||||
) : InvocationHandler {
|
) : InvocationHandler {
|
||||||
|
|
||||||
private enum class State {
|
private enum class State {
|
||||||
@ -97,12 +100,18 @@ class RPCClientProxyHandler(
|
|||||||
// To check whether toString() is being invoked
|
// To check whether toString() is being invoked
|
||||||
val toStringMethod: Method = Object::toString.javaMethod!!
|
val toStringMethod: Method = Object::toString.javaMethod!!
|
||||||
|
|
||||||
private fun addRpcCallSiteToThrowable(throwable: Throwable, callSite: Throwable) {
|
private fun addRpcCallSiteToThrowable(throwable: Throwable, callSite: CallSite) {
|
||||||
var currentThrowable = throwable
|
var currentThrowable = throwable
|
||||||
while (true) {
|
while (true) {
|
||||||
val cause = currentThrowable.cause
|
val cause = currentThrowable.cause
|
||||||
if (cause == null) {
|
if (cause == null) {
|
||||||
currentThrowable.initCause(callSite)
|
try {
|
||||||
|
currentThrowable.initCause(callSite)
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
// OK, we did our best, but the first throwable with a null cause was instantiated using
|
||||||
|
// Throwable(Throwable) or Throwable(String, Throwable) which means initCause can't ever
|
||||||
|
// be called even if it was passed null.
|
||||||
|
}
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
currentThrowable = cause
|
currentThrowable = cause
|
||||||
@ -146,15 +155,17 @@ class RPCClientProxyHandler(
|
|||||||
private fun createRpcObservableMap(): RpcObservableMap {
|
private fun createRpcObservableMap(): RpcObservableMap {
|
||||||
val onObservableRemove = RemovalListener<InvocationId, UnicastSubject<Notification<*>>> { key, _, cause ->
|
val onObservableRemove = RemovalListener<InvocationId, UnicastSubject<Notification<*>>> { key, _, cause ->
|
||||||
val observableId = key!!
|
val observableId = key!!
|
||||||
val rpcCallSite = callSiteMap?.remove(observableId)
|
val rpcCallSite: CallSite? = callSiteMap?.remove(observableId)
|
||||||
if (cause == RemovalCause.COLLECTED) {
|
if (cause == RemovalCause.COLLECTED) {
|
||||||
log.warn(listOf(
|
log.warn(listOf(
|
||||||
"A hot observable returned from an RPC was never subscribed to.",
|
"A hot observable returned from an RPC was never subscribed to.",
|
||||||
"This wastes server-side resources because it was queueing observations for retrieval.",
|
"This wastes server-side resources because it was queueing observations for retrieval.",
|
||||||
"It is being closed now, but please adjust your code to call .notUsed() on the observable",
|
"It is being closed now, but please adjust your code to call .notUsed() on the observable",
|
||||||
"to close it explicitly. (Java users: subscribe to it then unsubscribe). This warning",
|
"to close it explicitly. (Java users: subscribe to it then unsubscribe). If you aren't sure",
|
||||||
"will appear less frequently in future versions of the platform and you can ignore it",
|
"where the leak is coming from, set -Dnet.corda.client.rpc.trackRpcCallSites=true on the JVM",
|
||||||
"if you want to.").joinToString(" "), rpcCallSite)
|
"command line and you will get a stack trace with this warning."
|
||||||
|
).joinToString(" "), rpcCallSite)
|
||||||
|
rpcCallSite?.printStackTrace()
|
||||||
}
|
}
|
||||||
observablesToReap.locked { observables.add(observableId) }
|
observablesToReap.locked { observables.add(observableId) }
|
||||||
}
|
}
|
||||||
@ -215,6 +226,9 @@ class RPCClientProxyHandler(
|
|||||||
startSessions()
|
startSessions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A throwable that doesn't represent a real error - it's just here to wrap a stack trace. */
|
||||||
|
class CallSite(val rpcName: String) : Throwable("<Call site of root RPC '$rpcName'>")
|
||||||
|
|
||||||
// This is the general function that transforms a client side RPC to internal Artemis messages.
|
// This is the general function that transforms a client side RPC to internal Artemis messages.
|
||||||
override fun invoke(proxy: Any, method: Method, arguments: Array<out Any?>?): Any? {
|
override fun invoke(proxy: Any, method: Method, arguments: Array<out Any?>?): Any? {
|
||||||
lifeCycle.requireState { it == State.STARTED || it == State.SERVER_VERSION_NOT_SET }
|
lifeCycle.requireState { it == State.STARTED || it == State.SERVER_VERSION_NOT_SET }
|
||||||
@ -230,7 +244,7 @@ class RPCClientProxyHandler(
|
|||||||
throw RPCException("RPC server is not available.")
|
throw RPCException("RPC server is not available.")
|
||||||
|
|
||||||
val replyId = InvocationId.newInstance()
|
val replyId = InvocationId.newInstance()
|
||||||
callSiteMap?.set(replyId, Throwable("<Call site of root RPC '${method.name}'>"))
|
callSiteMap?.set(replyId, CallSite(method.name))
|
||||||
try {
|
try {
|
||||||
val serialisedArguments = (arguments?.toList() ?: emptyList()).serialize(context = serializationContextWithObservableContext)
|
val serialisedArguments = (arguments?.toList() ?: emptyList()).serialize(context = serializationContextWithObservableContext)
|
||||||
val request = RPCApi.ClientToServer.RpcRequest(
|
val request = RPCApi.ClientToServer.RpcRequest(
|
||||||
@ -263,6 +277,9 @@ class RPCClientProxyHandler(
|
|||||||
private fun sendMessage(message: RPCApi.ClientToServer) {
|
private fun sendMessage(message: RPCApi.ClientToServer) {
|
||||||
val artemisMessage = producerSession!!.createMessage(false)
|
val artemisMessage = producerSession!!.createMessage(false)
|
||||||
message.writeToClientMessage(artemisMessage)
|
message.writeToClientMessage(artemisMessage)
|
||||||
|
targetLegalIdentity?.let {
|
||||||
|
artemisMessage.putStringProperty(RPCApi.RPC_TARGET_LEGAL_IDENTITY, it.toString())
|
||||||
|
}
|
||||||
sendExecutor!!.submit {
|
sendExecutor!!.submit {
|
||||||
artemisMessage.putLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME, deduplicationSequenceNumber.getAndIncrement())
|
artemisMessage.putLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME, deduplicationSequenceNumber.getAndIncrement())
|
||||||
log.debug { "-> RPC -> $message" }
|
log.debug { "-> RPC -> $message" }
|
||||||
@ -273,7 +290,7 @@ class RPCClientProxyHandler(
|
|||||||
// The handler for Artemis messages.
|
// The handler for Artemis messages.
|
||||||
private fun artemisMessageHandler(message: ClientMessage) {
|
private fun artemisMessageHandler(message: ClientMessage) {
|
||||||
fun completeExceptionally(id: InvocationId, e: Throwable, future: SettableFuture<Any?>?) {
|
fun completeExceptionally(id: InvocationId, e: Throwable, future: SettableFuture<Any?>?) {
|
||||||
val rpcCallSite: Throwable? = callSiteMap?.get(id)
|
val rpcCallSite: CallSite? = callSiteMap?.get(id)
|
||||||
if (rpcCallSite != null) addRpcCallSiteToThrowable(e, rpcCallSite)
|
if (rpcCallSite != null) addRpcCallSiteToThrowable(e, rpcCallSite)
|
||||||
future?.setException(e.cause ?: e)
|
future?.setException(e.cause ?: e)
|
||||||
}
|
}
|
||||||
@ -536,7 +553,7 @@ class RPCClientProxyHandler(
|
|||||||
m.keys.forEach { k ->
|
m.keys.forEach { k ->
|
||||||
observationExecutorPool.run(k) {
|
observationExecutorPool.run(k) {
|
||||||
try {
|
try {
|
||||||
m[k]?.onError(RPCException("Connection failure detected."))
|
m[k]?.onError(ConnectionFailureException())
|
||||||
} catch (th: Throwable) {
|
} catch (th: Throwable) {
|
||||||
log.error("Unexpected exception when RPC connection failure handling", th)
|
log.error("Unexpected exception when RPC connection failure handling", th)
|
||||||
}
|
}
|
||||||
@ -545,7 +562,7 @@ class RPCClientProxyHandler(
|
|||||||
observableContext.observableMap.invalidateAll()
|
observableContext.observableMap.invalidateAll()
|
||||||
|
|
||||||
rpcReplyMap.forEach { _, replyFuture ->
|
rpcReplyMap.forEach { _, replyFuture ->
|
||||||
replyFuture.setException(RPCException("Connection failure detected."))
|
replyFuture.setException(ConnectionFailureException())
|
||||||
}
|
}
|
||||||
|
|
||||||
rpcReplyMap.clear()
|
rpcReplyMap.clear()
|
||||||
@ -555,13 +572,14 @@ class RPCClientProxyHandler(
|
|||||||
|
|
||||||
private typealias RpcObservableMap = Cache<InvocationId, UnicastSubject<Notification<*>>>
|
private typealias RpcObservableMap = Cache<InvocationId, UnicastSubject<Notification<*>>>
|
||||||
private typealias RpcReplyMap = ConcurrentHashMap<InvocationId, SettableFuture<Any?>>
|
private typealias RpcReplyMap = ConcurrentHashMap<InvocationId, SettableFuture<Any?>>
|
||||||
private typealias CallSiteMap = ConcurrentHashMap<InvocationId, Throwable?>
|
private typealias CallSiteMap = ConcurrentHashMap<InvocationId, RPCClientProxyHandler.CallSite?>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds a context available during de-serialisation of messages that are expected to contain Observables.
|
* Holds a context available during de-serialisation of messages that are expected to contain Observables.
|
||||||
*
|
*
|
||||||
* @param observableMap holds the Observables that are ultimately exposed to the user.
|
* @property observableMap holds the Observables that are ultimately exposed to the user.
|
||||||
* @param hardReferenceStore holds references to Observables we want to keep alive while they are subscribed to.
|
* @property hardReferenceStore holds references to Observables we want to keep alive while they are subscribed to.
|
||||||
|
* @property callSiteMap keeps stack traces captured when an RPC was invoked, useful for debugging when an observable leaks.
|
||||||
*/
|
*/
|
||||||
data class ObservableContext(
|
data class ObservableContext(
|
||||||
val callSiteMap: CallSiteMap?,
|
val callSiteMap: CallSiteMap?,
|
||||||
|
@ -2,8 +2,10 @@ package net.corda.client.rpc.internal.serialization.amqp
|
|||||||
|
|
||||||
|
|
||||||
import net.corda.client.rpc.internal.ObservableContext
|
import net.corda.client.rpc.internal.ObservableContext
|
||||||
|
import net.corda.client.rpc.internal.RPCClientProxyHandler
|
||||||
import net.corda.core.context.Trace
|
import net.corda.core.context.Trace
|
||||||
import net.corda.core.serialization.SerializationContext
|
import net.corda.core.serialization.SerializationContext
|
||||||
|
import net.corda.core.utilities.loggerFor
|
||||||
import net.corda.nodeapi.RPCApi
|
import net.corda.nodeapi.RPCApi
|
||||||
import net.corda.serialization.internal.amqp.*
|
import net.corda.serialization.internal.amqp.*
|
||||||
import org.apache.qpid.proton.codec.Data
|
import org.apache.qpid.proton.codec.Data
|
||||||
@ -17,11 +19,12 @@ import java.util.concurrent.atomic.AtomicInteger
|
|||||||
import javax.transaction.NotSupportedException
|
import javax.transaction.NotSupportedException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* De-serializer for Rx[Observable] instances for the RPC Client library. Can only be used to deserialize such objects,
|
* De-serializer for Rx [Observable] instances for the RPC Client library. Can only be used to deserialize such objects,
|
||||||
* just as the corresponding RPC server side code ([RpcServerObservableSerializer]) can only serialize them. Observables are only notionally serialized,
|
* just as the corresponding RPC server side class [RpcServerObservableSerializer] can only serialize them. Observables
|
||||||
* what is actually sent is a reference to the observable that can then be subscribed to.
|
* are only notionally serialized, what is actually sent is a reference to the observable that can then be subscribed to.
|
||||||
*/
|
*/
|
||||||
object RpcClientObservableDeSerializer : CustomSerializer.Implements<Observable<*>>(Observable::class.java) {
|
object RpcClientObservableDeSerializer : CustomSerializer.Implements<Observable<*>>(Observable::class.java) {
|
||||||
|
private val log = loggerFor<RpcClientObservableDeSerializer>()
|
||||||
private object RpcObservableContextKey
|
private object RpcObservableContextKey
|
||||||
|
|
||||||
fun createContext(
|
fun createContext(
|
||||||
@ -96,22 +99,23 @@ object RpcClientObservableDeSerializer : CustomSerializer.Implements<Observable<
|
|||||||
}
|
}
|
||||||
|
|
||||||
val rpcCallSite = getRpcCallSite(context, observableContext)
|
val rpcCallSite = getRpcCallSite(context, observableContext)
|
||||||
|
|
||||||
observableContext.observableMap.put(observableId, observable)
|
observableContext.observableMap.put(observableId, observable)
|
||||||
observableContext.callSiteMap?.put(observableId, rpcCallSite)
|
observableContext.callSiteMap?.put(observableId, rpcCallSite)
|
||||||
|
log.trace("Deserialising observable $observableId", rpcCallSite)
|
||||||
|
|
||||||
// We pin all Observables into a hard reference store (rooted in the RPC proxy) on subscription so that users
|
// We pin all Observables into a hard reference store (rooted in the RPC proxy) on subscription so that users
|
||||||
// don't need to store a reference to the Observables themselves.
|
// don't need to store a reference to the Observables themselves.
|
||||||
return pinInSubscriptions(observable, observableContext.hardReferenceStore).doOnUnsubscribe {
|
return pinInSubscriptions(observable, observableContext.hardReferenceStore).doOnUnsubscribe {
|
||||||
// This causes Future completions to give warnings because the corresponding OnComplete sent from the server
|
// This causes Future completions to give warnings because the corresponding OnComplete sent from the server
|
||||||
// will arrive after the client unsubscribes from the observable and consequently invalidates the mapping.
|
// will arrive after the client unsubscribes from the observable and consequently invalidates the mapping.
|
||||||
// The unsubscribe is due to [ObservableToFuture]'s use of first().
|
// The unsubscribe is due to ObservableToFuture's use of first().
|
||||||
observableContext.observableMap.invalidate(observableId)
|
observableContext.observableMap.invalidate(observableId)
|
||||||
}.dematerialize<Any>()
|
}.dematerialize<Any>()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getRpcCallSite(context: SerializationContext, observableContext: ObservableContext): Throwable? {
|
private fun getRpcCallSite(context: SerializationContext, observableContext: ObservableContext): RPCClientProxyHandler.CallSite? {
|
||||||
val rpcRequestOrObservableId = context.properties[RPCApi.RpcRequestOrObservableIdKey] as Trace.InvocationId
|
val rpcRequestOrObservableId = context.properties[RPCApi.RpcRequestOrObservableIdKey] as Trace.InvocationId
|
||||||
|
// Will only return non-null if the trackRpcCallSites option in the RPC configuration has been specified.
|
||||||
return observableContext.callSiteMap?.get(rpcRequestOrObservableId)
|
return observableContext.callSiteMap?.get(rpcRequestOrObservableId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ class ClientRPCInfrastructureTests : AbstractRPCTest() {
|
|||||||
|
|
||||||
fun makeComplicatedListenableFuture(): CordaFuture<Pair<String, CordaFuture<String>>>
|
fun makeComplicatedListenableFuture(): CordaFuture<Pair<String, CordaFuture<String>>>
|
||||||
|
|
||||||
@RPCSinceVersion(2)
|
@RPCSinceVersion(2000)
|
||||||
fun addedLater()
|
fun addedLater()
|
||||||
|
|
||||||
fun captureUser(): String
|
fun captureUser(): String
|
||||||
@ -58,7 +58,7 @@ class ClientRPCInfrastructureTests : AbstractRPCTest() {
|
|||||||
private lateinit var complicatedListenableFuturee: CordaFuture<Pair<String, CordaFuture<String>>>
|
private lateinit var complicatedListenableFuturee: CordaFuture<Pair<String, CordaFuture<String>>>
|
||||||
|
|
||||||
inner class TestOpsImpl : TestOps {
|
inner class TestOpsImpl : TestOps {
|
||||||
override val protocolVersion = 1
|
override val protocolVersion = 1000
|
||||||
// do not remove Unit
|
// do not remove Unit
|
||||||
override fun barf(): Unit = throw IllegalArgumentException("Barf!")
|
override fun barf(): Unit = throw IllegalArgumentException("Barf!")
|
||||||
override fun void() {}
|
override fun void() {}
|
||||||
|
@ -33,7 +33,7 @@ class RPCConcurrencyTests : AbstractRPCTest() {
|
|||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class ObservableRose<out A>(val value: A, val branches: Observable<out ObservableRose<A>>)
|
data class ObservableRose<out A>(val value: A, val branches: Observable<out ObservableRose<A>>)
|
||||||
|
|
||||||
private interface TestOps : RPCOps {
|
interface TestOps : RPCOps {
|
||||||
fun newLatch(numberOfDowns: Int): Long
|
fun newLatch(numberOfDowns: Int): Long
|
||||||
fun waitLatch(id: Long)
|
fun waitLatch(id: Long)
|
||||||
fun downLatch(id: Long)
|
fun downLatch(id: Long)
|
||||||
@ -43,7 +43,7 @@ class RPCConcurrencyTests : AbstractRPCTest() {
|
|||||||
|
|
||||||
class TestOpsImpl(private val pool: Executor) : TestOps {
|
class TestOpsImpl(private val pool: Executor) : TestOps {
|
||||||
private val latches = ConcurrentHashMap<Long, CountDownLatch>()
|
private val latches = ConcurrentHashMap<Long, CountDownLatch>()
|
||||||
override val protocolVersion = 0
|
override val protocolVersion = 1000
|
||||||
|
|
||||||
override fun newLatch(numberOfDowns: Int): Long {
|
override fun newLatch(numberOfDowns: Int): Long {
|
||||||
val id = random63BitValue()
|
val id = random63BitValue()
|
||||||
|
@ -26,7 +26,7 @@ class RPCFailureTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class OpsImpl : Ops {
|
class OpsImpl : Ops {
|
||||||
override val protocolVersion = 1
|
override val protocolVersion = 1000
|
||||||
override fun getUnserializable() = Unserializable()
|
override fun getUnserializable() = Unserializable()
|
||||||
override fun getUnserializableAsync(): CordaFuture<Unserializable> {
|
override fun getUnserializableAsync(): CordaFuture<Unserializable> {
|
||||||
return openFuture<Unserializable>().apply { capture { getUnserializable() } }
|
return openFuture<Unserializable>().apply { capture { getUnserializable() } }
|
||||||
|
@ -24,7 +24,7 @@ class RPCHighThroughputObservableTests : AbstractRPCTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal class TestOpsImpl : TestOps {
|
internal class TestOpsImpl : TestOps {
|
||||||
override val protocolVersion = 1
|
override val protocolVersion = 1000
|
||||||
|
|
||||||
override fun makeObservable(): Observable<Int> = Observable.interval(0, TimeUnit.MICROSECONDS).map { it.toInt() + 1 }
|
override fun makeObservable(): Observable<Int> = Observable.interval(0, TimeUnit.MICROSECONDS).map { it.toInt() + 1 }
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,8 @@ import net.corda.core.messaging.RPCOps
|
|||||||
import net.corda.core.utilities.minutes
|
import net.corda.core.utilities.minutes
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.node.services.messaging.RPCServerConfiguration
|
import net.corda.node.services.messaging.RPCServerConfiguration
|
||||||
import net.corda.testing.node.internal.RPCDriverDSL
|
|
||||||
import net.corda.testing.internal.performance.div
|
import net.corda.testing.internal.performance.div
|
||||||
|
import net.corda.testing.node.internal.RPCDriverDSL
|
||||||
import net.corda.testing.node.internal.performance.startPublishingFixedRateInjector
|
import net.corda.testing.node.internal.performance.startPublishingFixedRateInjector
|
||||||
import net.corda.testing.node.internal.performance.startReporter
|
import net.corda.testing.node.internal.performance.startReporter
|
||||||
import net.corda.testing.node.internal.performance.startTightLoopInjector
|
import net.corda.testing.node.internal.performance.startTightLoopInjector
|
||||||
@ -34,7 +34,7 @@ class RPCPerformanceTests : AbstractRPCTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class TestOpsImpl : TestOps {
|
class TestOpsImpl : TestOps {
|
||||||
override val protocolVersion = 0
|
override val protocolVersion = 1000
|
||||||
override fun simpleReply(input: ByteArray, sizeOfReply: Int): ByteArray {
|
override fun simpleReply(input: ByteArray, sizeOfReply: Int): ByteArray {
|
||||||
return ByteArray(sizeOfReply)
|
return ByteArray(sizeOfReply)
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ class RPCPermissionsTests : AbstractRPCTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class TestOpsImpl : TestOps {
|
class TestOpsImpl : TestOps {
|
||||||
override val protocolVersion = 1
|
override val protocolVersion = 1000
|
||||||
override fun validatePermission(method: String, target: String?) {
|
override fun validatePermission(method: String, target: String?) {
|
||||||
val authorized = if (target == null) {
|
val authorized = if (target == null) {
|
||||||
rpcContext().isPermitted(method)
|
rpcContext().isPermitted(method)
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
<Policies>
|
<Policies>
|
||||||
<TimeBasedTriggeringPolicy/>
|
<TimeBasedTriggeringPolicy/>
|
||||||
<SizeBasedTriggeringPolicy size="10MB"/>
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
</Policies>
|
</Policies>
|
||||||
|
|
||||||
<DefaultRolloverStrategy min="1" max="100">
|
<DefaultRolloverStrategy min="1" max="100">
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
gradlePluginsVersion=4.0.29
|
gradlePluginsVersion=4.0.29
|
||||||
kotlinVersion=1.2.51
|
kotlinVersion=1.2.51
|
||||||
|
# When adjusting platformVersion upwards please also modify CordaRPCClientConfiguration.minimumServerProtocolVersion \
|
||||||
|
# if there have been any RPC changes. Also please modify InternalMockNetwork.kt:MOCK_VERSION_INFO and NodeBasedTest.startNode
|
||||||
platformVersion=4
|
platformVersion=4
|
||||||
guavaVersion=25.1-jre
|
guavaVersion=25.1-jre
|
||||||
proguardVersion=6.0.3
|
proguardVersion=6.0.3
|
||||||
bouncycastleVersion=1.57
|
bouncycastleVersion=1.60
|
||||||
|
disruptorVersion=3.4.2
|
||||||
typesafeConfigVersion=1.3.1
|
typesafeConfigVersion=1.3.1
|
||||||
jsr305Version=3.0.2
|
jsr305Version=3.0.2
|
||||||
artifactoryPluginVersion=4.7.3
|
artifactoryPluginVersion=4.7.3
|
||||||
|
@ -11,8 +11,8 @@ def javaHome = System.getProperty('java.home')
|
|||||||
def jarBaseName = "corda-${project.name}".toString()
|
def jarBaseName = "corda-${project.name}".toString()
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
runtimeLibraries
|
deterministicLibraries
|
||||||
runtimeArtifacts.extendsFrom runtimeLibraries
|
deterministicArtifacts.extendsFrom deterministicLibraries
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -20,14 +20,14 @@ dependencies {
|
|||||||
|
|
||||||
// Configure these by hand. It should be a minimal subset of core's dependencies,
|
// Configure these by hand. It should be a minimal subset of core's dependencies,
|
||||||
// and without any obviously non-deterministic ones such as Hibernate.
|
// and without any obviously non-deterministic ones such as Hibernate.
|
||||||
runtimeLibraries "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
deterministicLibraries "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
runtimeLibraries "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
deterministicLibraries "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
runtimeLibraries "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final"
|
deterministicLibraries "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final"
|
||||||
runtimeLibraries "org.bouncycastle:bcprov-jdk15on:$bouncycastle_version"
|
deterministicLibraries "org.bouncycastle:bcprov-jdk15on:$bouncycastle_version"
|
||||||
runtimeLibraries "org.bouncycastle:bcpkix-jdk15on:$bouncycastle_version"
|
deterministicLibraries "org.bouncycastle:bcpkix-jdk15on:$bouncycastle_version"
|
||||||
runtimeLibraries "com.google.code.findbugs:jsr305:$jsr305_version"
|
deterministicLibraries "com.google.code.findbugs:jsr305:$jsr305_version"
|
||||||
runtimeLibraries "net.i2p.crypto:eddsa:$eddsa_version"
|
deterministicLibraries "net.i2p.crypto:eddsa:$eddsa_version"
|
||||||
runtimeLibraries "org.slf4j:slf4j-api:$slf4j_version"
|
deterministicLibraries "org.slf4j:slf4j-api:$slf4j_version"
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
@ -50,6 +50,7 @@ task patchCore(type: Zip, dependsOn: coreJarTask) {
|
|||||||
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'
|
||||||
|
exclude 'net/corda/core/serialization/internal/CheckpointSerializationFactory*.class'
|
||||||
}
|
}
|
||||||
|
|
||||||
reproducibleFileOrder = true
|
reproducibleFileOrder = true
|
||||||
@ -112,7 +113,7 @@ task determinise(type: ProGuardTask) {
|
|||||||
|
|
||||||
libraryjars file("$javaHome/lib/rt.jar")
|
libraryjars file("$javaHome/lib/rt.jar")
|
||||||
libraryjars file("$javaHome/lib/jce.jar")
|
libraryjars file("$javaHome/lib/jce.jar")
|
||||||
configurations.runtimeLibraries.forEach {
|
configurations.deterministicLibraries.forEach {
|
||||||
libraryjars it, filter: '!META-INF/versions/**'
|
libraryjars it, filter: '!META-INF/versions/**'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,7 +153,7 @@ task checkDeterminism(type: ProGuardTask, dependsOn: jdkTask) {
|
|||||||
|
|
||||||
libraryjars deterministic_rt_jar
|
libraryjars deterministic_rt_jar
|
||||||
|
|
||||||
configurations.runtimeLibraries.forEach {
|
configurations.deterministicLibraries.forEach {
|
||||||
libraryjars it, filter: '!META-INF/versions/**'
|
libraryjars it, filter: '!META-INF/versions/**'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,12 +174,12 @@ assemble.dependsOn checkDeterminism
|
|||||||
|
|
||||||
def deterministicJar = metafix.outputs.files.singleFile
|
def deterministicJar = metafix.outputs.files.singleFile
|
||||||
artifacts {
|
artifacts {
|
||||||
runtimeArtifacts file: deterministicJar, name: jarBaseName, type: 'jar', extension: 'jar', builtBy: metafix
|
deterministicArtifacts file: deterministicJar, name: jarBaseName, type: 'jar', extension: 'jar', builtBy: metafix
|
||||||
publish file: deterministicJar, name: jarBaseName, type: 'jar', extension: 'jar', builtBy: metafix
|
publish file: deterministicJar, name: jarBaseName, type: 'jar', extension: 'jar', builtBy: metafix
|
||||||
}
|
}
|
||||||
|
|
||||||
publish {
|
publish {
|
||||||
dependenciesFrom configurations.runtimeArtifacts
|
dependenciesFrom configurations.deterministicArtifacts
|
||||||
publishSources = false
|
publishSources = false
|
||||||
publishJavadoc = false
|
publishJavadoc = false
|
||||||
name jarBaseName
|
name jarBaseName
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
package net.corda.core.serialization.internal
|
||||||
|
|
||||||
|
import net.corda.core.KeepForDJVM
|
||||||
|
import net.corda.core.serialization.SerializedBytes
|
||||||
|
import net.corda.core.utilities.ByteSequence
|
||||||
|
import java.io.NotSerializableException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A deterministic version of [CheckpointSerializationFactory] that does not use thread-locals to manage serialization
|
||||||
|
* context.
|
||||||
|
*/
|
||||||
|
@KeepForDJVM
|
||||||
|
class CheckpointSerializationFactory(
|
||||||
|
private val scheme: CheckpointSerializationScheme
|
||||||
|
) {
|
||||||
|
|
||||||
|
val defaultContext: CheckpointSerializationContext get() = _currentContext ?: effectiveSerializationEnv.checkpointContext
|
||||||
|
|
||||||
|
private val creator: List<StackTraceElement> = Exception().stackTrace.asList()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize the bytes in to an object, using the prefixed bytes to determine the format.
|
||||||
|
*
|
||||||
|
* @param byteSequence The bytes to deserialize, including a format header prefix.
|
||||||
|
* @param clazz The class or superclass or the object to be deserialized, or [Any] or [Object] if unknown.
|
||||||
|
* @param context A context that configures various parameters to deserialization.
|
||||||
|
*/
|
||||||
|
@Throws(NotSerializableException::class)
|
||||||
|
fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: CheckpointSerializationContext): T {
|
||||||
|
return withCurrentContext(context) { scheme.deserialize(byteSequence, clazz, context) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize an object to bytes using the preferred serialization format version from the context.
|
||||||
|
*
|
||||||
|
* @param obj The object to be serialized.
|
||||||
|
* @param context A context that configures various parameters to serialization, including the serialization format version.
|
||||||
|
*/
|
||||||
|
fun <T : Any> serialize(obj: T, context: CheckpointSerializationContext): SerializedBytes<T> {
|
||||||
|
return withCurrentContext(context) { scheme.serialize(obj, context) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "${this.javaClass.name} scheme=$scheme ${creator.joinToString("\n")}"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return other is CheckpointSerializationFactory && other.scheme == this.scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int = scheme.hashCode()
|
||||||
|
|
||||||
|
private var _currentContext: CheckpointSerializationContext? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the current context inside the block to that supplied.
|
||||||
|
*/
|
||||||
|
fun <T> withCurrentContext(context: CheckpointSerializationContext?, block: () -> T): T {
|
||||||
|
val priorContext = _currentContext
|
||||||
|
if (context != null) _currentContext = context
|
||||||
|
try {
|
||||||
|
return block()
|
||||||
|
} finally {
|
||||||
|
if (context != null) _currentContext = priorContext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* A default factory for serialization/deserialization.
|
||||||
|
*/
|
||||||
|
val defaultFactory: CheckpointSerializationFactory get() = effectiveSerializationEnv.checkpointSerializationFactory
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,10 @@
|
|||||||
apply plugin: 'kotlin'
|
apply plugin: 'kotlin'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testCompile project(path: ':core-deterministic', configuration: 'runtimeArtifacts')
|
testCompile project(path: ':core-deterministic', configuration: 'deterministicArtifacts')
|
||||||
testCompile project(path: ':serialization-deterministic', configuration: 'runtimeArtifacts')
|
testCompile project(path: ':serialization-deterministic', configuration: 'deterministicArtifacts')
|
||||||
|
testCompile project(path: ':core-deterministic:testing:verifier', configuration: 'deterministicArtifacts')
|
||||||
testCompile project(path: ':core-deterministic:testing:data', configuration: 'testData')
|
testCompile project(path: ':core-deterministic:testing:data', configuration: 'testData')
|
||||||
testCompile project(':core-deterministic:testing:common')
|
|
||||||
testCompile(project(':finance')) {
|
testCompile(project(':finance')) {
|
||||||
transitive = false
|
transitive = false
|
||||||
}
|
}
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
apply from: '../../../deterministic.gradle'
|
|
||||||
apply plugin: 'idea'
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compileOnly project(path: ':core-deterministic', configuration: 'runtimeArtifacts')
|
|
||||||
compileOnly project(path: ':serialization-deterministic', configuration: 'runtimeArtifacts')
|
|
||||||
compileOnly "junit:junit:$junit_version"
|
|
||||||
}
|
|
||||||
|
|
||||||
idea {
|
|
||||||
module {
|
|
||||||
if (project.hasProperty("deterministic_idea_sdk")) {
|
|
||||||
jdkName project.property("deterministic_idea_sdk") as String
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,7 +8,7 @@ dependencies {
|
|||||||
testCompile project(':core')
|
testCompile project(':core')
|
||||||
testCompile project(':finance')
|
testCompile project(':finance')
|
||||||
testCompile project(':node-driver')
|
testCompile project(':node-driver')
|
||||||
testCompile project(':core-deterministic:testing:common')
|
testCompile project(path: ':core-deterministic:testing:verifier', configuration: 'runtimeArtifacts')
|
||||||
|
|
||||||
testCompile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
testCompile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
testCompile "org.jetbrains.kotlin:kotlin-reflect"
|
testCompile "org.jetbrains.kotlin:kotlin-reflect"
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package net.corda.deterministic.data
|
package net.corda.deterministic.data
|
||||||
|
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.deterministic.common.LocalSerializationRule
|
import net.corda.deterministic.verifier.LocalSerializationRule
|
||||||
import net.corda.deterministic.common.TransactionVerificationRequest
|
import net.corda.deterministic.verifier.TransactionVerificationRequest
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -7,9 +7,9 @@ import net.corda.core.identity.AnonymousParty
|
|||||||
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.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.deterministic.common.MockContractAttachment
|
import net.corda.deterministic.verifier.MockContractAttachment
|
||||||
import net.corda.deterministic.common.SampleCommandData
|
import net.corda.deterministic.verifier.SampleCommandData
|
||||||
import net.corda.deterministic.common.TransactionVerificationRequest
|
import net.corda.deterministic.verifier.TransactionVerificationRequest
|
||||||
import net.corda.finance.POUNDS
|
import net.corda.finance.POUNDS
|
||||||
import net.corda.finance.`issued by`
|
import net.corda.finance.`issued by`
|
||||||
import net.corda.finance.contracts.asset.Cash.*
|
import net.corda.finance.contracts.asset.Cash.*
|
||||||
|
@ -3,7 +3,7 @@ package net.corda.deterministic.crypto
|
|||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.deterministic.KeyStoreProvider
|
import net.corda.deterministic.KeyStoreProvider
|
||||||
import net.corda.deterministic.CheatingSecurityProvider
|
import net.corda.deterministic.CheatingSecurityProvider
|
||||||
import net.corda.deterministic.common.LocalSerializationRule
|
import net.corda.deterministic.verifier.LocalSerializationRule
|
||||||
import org.junit.*
|
import org.junit.*
|
||||||
import org.junit.rules.RuleChain
|
import org.junit.rules.RuleChain
|
||||||
import java.security.*
|
import java.security.*
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
@file:JvmName("Enclavelet")
|
|
||||||
package net.corda.deterministic.txverify
|
|
||||||
|
|
||||||
import net.corda.core.serialization.deserialize
|
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
|
||||||
import net.corda.deterministic.bytesOfResource
|
|
||||||
import net.corda.deterministic.common.LocalSerializationRule
|
|
||||||
import net.corda.deterministic.common.TransactionVerificationRequest
|
|
||||||
import net.corda.finance.contracts.asset.Cash.Commands.*
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
|
||||||
import org.junit.ClassRule
|
|
||||||
import org.junit.Test
|
|
||||||
import kotlin.test.assertFailsWith
|
|
||||||
|
|
||||||
class EnclaveletTest {
|
|
||||||
companion object {
|
|
||||||
@ClassRule
|
|
||||||
@JvmField
|
|
||||||
val serialization = LocalSerializationRule(EnclaveletTest::class)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun success() {
|
|
||||||
verifyInEnclave(bytesOfResource("txverify/tx-success.bin"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun failure() {
|
|
||||||
val e = assertFailsWith<Exception> { verifyInEnclave(bytesOfResource("txverify/tx-failure.bin")) }
|
|
||||||
assertThat(e).hasMessageContaining("Required ${Move::class.java.canonicalName} command")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns either null to indicate success when the transactions are validated, or a string with the
|
|
||||||
* contents of the error. Invoked via JNI in response to an enclave RPC. The argument is a serialised
|
|
||||||
* [TransactionVerificationRequest].
|
|
||||||
*
|
|
||||||
* Note that it is assumed the signatures were already checked outside the sandbox: the purpose of this code
|
|
||||||
* is simply to check the sensitive, app specific parts of a transaction.
|
|
||||||
*
|
|
||||||
* TODO: Transaction data is meant to be encrypted under an enclave-private key.
|
|
||||||
*/
|
|
||||||
@Throws(Exception::class)
|
|
||||||
private fun verifyInEnclave(reqBytes: ByteArray) {
|
|
||||||
deserialize(reqBytes).verify()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deserialize(reqBytes: ByteArray): LedgerTransaction {
|
|
||||||
return reqBytes.deserialize<TransactionVerificationRequest>()
|
|
||||||
.toLedgerTransaction()
|
|
||||||
}
|
|
@ -0,0 +1,29 @@
|
|||||||
|
package net.corda.deterministic.txverify
|
||||||
|
|
||||||
|
import net.corda.deterministic.bytesOfResource
|
||||||
|
import net.corda.deterministic.verifier.LocalSerializationRule
|
||||||
|
import net.corda.deterministic.verifier.verifyTransaction
|
||||||
|
import net.corda.finance.contracts.asset.Cash.Commands.*
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.ClassRule
|
||||||
|
import org.junit.Test
|
||||||
|
import kotlin.test.assertFailsWith
|
||||||
|
|
||||||
|
class VerifyTransactionTest {
|
||||||
|
companion object {
|
||||||
|
@ClassRule
|
||||||
|
@JvmField
|
||||||
|
val serialization = LocalSerializationRule(VerifyTransactionTest::class)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun success() {
|
||||||
|
verifyTransaction(bytesOfResource("txverify/tx-success.bin"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun failure() {
|
||||||
|
val e = assertFailsWith<Exception> { verifyTransaction(bytesOfResource("txverify/tx-failure.bin")) }
|
||||||
|
assertThat(e).hasMessageContaining("Required ${Move::class.java.canonicalName} command")
|
||||||
|
}
|
||||||
|
}
|
48
core-deterministic/testing/verifier/build.gradle
Normal file
48
core-deterministic/testing/verifier/build.gradle
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
apply plugin: 'java-library'
|
||||||
|
apply from: '../../../deterministic.gradle'
|
||||||
|
apply plugin: 'net.corda.plugins.publish-utils'
|
||||||
|
apply plugin: 'com.jfrog.artifactory'
|
||||||
|
apply plugin: 'idea'
|
||||||
|
|
||||||
|
description 'Test utilities for deterministic contract verification'
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
deterministicArtifacts
|
||||||
|
runtimeArtifacts.extendsFrom api
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
deterministicArtifacts project(path: ':serialization-deterministic', configuration: 'deterministicArtifacts')
|
||||||
|
deterministicArtifacts project(path: ':core-deterministic', configuration: 'deterministicArtifacts')
|
||||||
|
|
||||||
|
runtimeArtifacts project(':serialization')
|
||||||
|
runtimeArtifacts project(':core')
|
||||||
|
|
||||||
|
// Compile against the deterministic artifacts to ensure that we use only the deterministic API subset.
|
||||||
|
compileOnly configurations.deterministicArtifacts
|
||||||
|
api "junit:junit:$junit_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
baseName 'corda-deterministic-verifier'
|
||||||
|
}
|
||||||
|
|
||||||
|
artifacts {
|
||||||
|
deterministicArtifacts jar
|
||||||
|
runtimeArtifacts jar
|
||||||
|
publish jar
|
||||||
|
}
|
||||||
|
|
||||||
|
publish {
|
||||||
|
// Our published POM will contain dependencies on the non-deterministic Corda artifacts.
|
||||||
|
dependenciesFrom configurations.runtimeArtifacts
|
||||||
|
name jar.baseName
|
||||||
|
}
|
||||||
|
|
||||||
|
idea {
|
||||||
|
module {
|
||||||
|
if (project.hasProperty("deterministic_idea_sdk")) {
|
||||||
|
jdkName project.property("deterministic_idea_sdk") as String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package net.corda.deterministic.common
|
package net.corda.deterministic.verifier
|
||||||
|
|
||||||
import net.corda.core.serialization.ClassWhitelist
|
import net.corda.core.serialization.ClassWhitelist
|
||||||
import net.corda.core.serialization.SerializationContext
|
import net.corda.core.serialization.SerializationContext
|
||||||
@ -83,4 +83,4 @@ class LocalSerializationRule(private val label: String) : TestRule {
|
|||||||
return canDeserializeVersion(magic) && target == P2P
|
return canDeserializeVersion(magic) && target == P2P
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package net.corda.deterministic.common
|
package net.corda.deterministic.verifier
|
||||||
|
|
||||||
import net.corda.core.contracts.Attachment
|
import net.corda.core.contracts.Attachment
|
||||||
import net.corda.core.contracts.ContractClassName
|
import net.corda.core.contracts.ContractClassName
|
@ -1,5 +1,5 @@
|
|||||||
@file:JvmName("SampleData")
|
@file:JvmName("SampleData")
|
||||||
package net.corda.deterministic.common
|
package net.corda.deterministic.verifier
|
||||||
|
|
||||||
import net.corda.core.contracts.TypeOnlyCommandData
|
import net.corda.core.contracts.TypeOnlyCommandData
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package net.corda.deterministic.common
|
package net.corda.deterministic.verifier
|
||||||
|
|
||||||
import net.corda.core.contracts.Attachment
|
import net.corda.core.contracts.Attachment
|
||||||
import net.corda.core.contracts.ContractAttachment
|
import net.corda.core.contracts.ContractAttachment
|
@ -0,0 +1,21 @@
|
|||||||
|
@file:JvmName("Verifier")
|
||||||
|
package net.corda.deterministic.verifier
|
||||||
|
|
||||||
|
import net.corda.core.serialization.deserialize
|
||||||
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We assume the signatures were already checked outside the sandbox: the purpose of this code
|
||||||
|
* is simply to check the sensitive, app-specific parts of a transaction.
|
||||||
|
*
|
||||||
|
* TODO: Transaction data is meant to be encrypted under an enclave-private key.
|
||||||
|
*/
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun verifyTransaction(reqBytes: ByteArray) {
|
||||||
|
deserialize(reqBytes).verify()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deserialize(reqBytes: ByteArray): LedgerTransaction {
|
||||||
|
return reqBytes.deserialize<TransactionVerificationRequest>()
|
||||||
|
.toLedgerTransaction()
|
||||||
|
}
|
@ -48,20 +48,4 @@ interface Cordapp {
|
|||||||
val jarPath: URL
|
val jarPath: URL
|
||||||
val cordappClasses: List<String>
|
val cordappClasses: List<String>
|
||||||
val jarHash: SecureHash.SHA256
|
val jarHash: SecureHash.SHA256
|
||||||
|
}
|
||||||
/**
|
|
||||||
* CorDapp's information, including vendor and version.
|
|
||||||
*
|
|
||||||
* @property shortName Cordapp's shortName
|
|
||||||
* @property vendor Cordapp's vendor
|
|
||||||
* @property version Cordapp's version
|
|
||||||
*/
|
|
||||||
@DoNotImplement
|
|
||||||
interface Info {
|
|
||||||
val shortName: String
|
|
||||||
val vendor: String
|
|
||||||
val version: String
|
|
||||||
|
|
||||||
fun hasUnknownFields(): Boolean
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -102,7 +102,11 @@ fun PublicKey.isValid(content: ByteArray, signature: DigitalSignature): Boolean
|
|||||||
/** Render a public key to its hash (in Base58) of its serialised form using the DL prefix. */
|
/** Render a public key to its hash (in Base58) of its serialised form using the DL prefix. */
|
||||||
fun PublicKey.toStringShort(): String = "DL" + this.toSHA256Bytes().toBase58()
|
fun PublicKey.toStringShort(): String = "DL" + this.toSHA256Bytes().toBase58()
|
||||||
|
|
||||||
/** Return a [Set] of the contained keys if this is a [CompositeKey]; otherwise, return a [Set] with a single element (this [PublicKey]). */
|
/**
|
||||||
|
* Return a [Set] of the contained leaf keys if this is a [CompositeKey].
|
||||||
|
* Otherwise, return a [Set] with a single element (this [PublicKey]).
|
||||||
|
* <i>Note that leaf keys cannot be of type [CompositeKey].</i>
|
||||||
|
*/
|
||||||
val PublicKey.keys: Set<PublicKey> get() = (this as? CompositeKey)?.leafKeys ?: setOf(this)
|
val PublicKey.keys: Set<PublicKey> get() = (this as? CompositeKey)?.leafKeys ?: setOf(this)
|
||||||
|
|
||||||
/** Return true if [otherKey] fulfils the requirements of this [PublicKey]. */
|
/** Return true if [otherKey] fulfils the requirements of this [PublicKey]. */
|
||||||
@ -110,7 +114,12 @@ fun PublicKey.isFulfilledBy(otherKey: PublicKey): Boolean = isFulfilledBy(setOf(
|
|||||||
/** Return true if [otherKeys] fulfil the requirements of this [PublicKey]. */
|
/** Return true if [otherKeys] fulfil the requirements of this [PublicKey]. */
|
||||||
fun PublicKey.isFulfilledBy(otherKeys: Iterable<PublicKey>): Boolean = (this as? CompositeKey)?.isFulfilledBy(otherKeys) ?: (this in otherKeys)
|
fun PublicKey.isFulfilledBy(otherKeys: Iterable<PublicKey>): Boolean = (this as? CompositeKey)?.isFulfilledBy(otherKeys) ?: (this in otherKeys)
|
||||||
|
|
||||||
/** Checks whether any of the given [keys] matches a leaf on the [CompositeKey] tree or a single [PublicKey]. */
|
/**
|
||||||
|
* Checks whether any of the given [keys] matches a leaf on the [CompositeKey] tree or a single [PublicKey].
|
||||||
|
*
|
||||||
|
* <i>Note that this function checks against leaves, which cannot be of type [CompositeKey]. Due to that, if any of the
|
||||||
|
* [otherKeys] is a [CompositeKey], this function will not find a match.</i>
|
||||||
|
*/
|
||||||
fun PublicKey.containsAny(otherKeys: Iterable<PublicKey>): Boolean {
|
fun PublicKey.containsAny(otherKeys: Iterable<PublicKey>): Boolean {
|
||||||
return if (this is CompositeKey) keys.intersect(otherKeys).isNotEmpty()
|
return if (this is CompositeKey) keys.intersect(otherKeys).isNotEmpty()
|
||||||
else this in otherKeys
|
else this in otherKeys
|
||||||
|
@ -11,9 +11,11 @@ import net.i2p.crypto.eddsa.EdDSASecurityProvider
|
|||||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier
|
import org.bouncycastle.asn1.ASN1ObjectIdentifier
|
||||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
|
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
|
||||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
||||||
|
import org.bouncycastle.jcajce.provider.asymmetric.ec.AlgorithmParametersSpi
|
||||||
import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter
|
import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider
|
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider
|
||||||
|
import java.security.SecureRandom
|
||||||
import java.security.Security
|
import java.security.Security
|
||||||
|
|
||||||
internal val cordaSecurityProvider = CordaSecurityProvider().also {
|
internal val cordaSecurityProvider = CordaSecurityProvider().also {
|
||||||
@ -29,6 +31,8 @@ internal val cordaBouncyCastleProvider = BouncyCastleProvider().apply {
|
|||||||
override fun generatePublic(keyInfo: SubjectPublicKeyInfo) = decodePublicKey(EDDSA_ED25519_SHA512, keyInfo.encoded)
|
override fun generatePublic(keyInfo: SubjectPublicKeyInfo) = decodePublicKey(EDDSA_ED25519_SHA512, keyInfo.encoded)
|
||||||
override fun generatePrivate(keyInfo: PrivateKeyInfo) = decodePrivateKey(EDDSA_ED25519_SHA512, keyInfo.encoded)
|
override fun generatePrivate(keyInfo: PrivateKeyInfo) = decodePrivateKey(EDDSA_ED25519_SHA512, keyInfo.encoded)
|
||||||
})
|
})
|
||||||
|
// Required due to [X509CRL].verify() reported issues in network-services after BC 1.60 update.
|
||||||
|
put("AlgorithmParameters.SHA256WITHECDSA", AlgorithmParametersSpi::class.java.name)
|
||||||
}.also {
|
}.also {
|
||||||
// This registration is needed for reading back EdDSA key from java keystore.
|
// This registration is needed for reading back EdDSA key from java keystore.
|
||||||
// TODO: Find a way to make JKS work with bouncy castle provider or implement our own provide so we don't have to register bouncy castle provider.
|
// TODO: Find a way to make JKS work with bouncy castle provider or implement our own provide so we don't have to register bouncy castle provider.
|
||||||
@ -46,4 +50,4 @@ internal val bouncyCastlePQCProvider = BouncyCastlePQCProvider().apply {
|
|||||||
internal val providerMap = listOf(cordaBouncyCastleProvider, cordaSecurityProvider, bouncyCastlePQCProvider).map { it.name to it }.toMap()
|
internal val providerMap = listOf(cordaBouncyCastleProvider, cordaSecurityProvider, bouncyCastlePQCProvider).map { it.name to it }.toMap()
|
||||||
|
|
||||||
@DeleteForDJVM
|
@DeleteForDJVM
|
||||||
internal fun platformSecureRandomFactory() = platformSecureRandom() // To minimise diff of CryptoUtils against open-source.
|
internal fun platformSecureRandomFactory(): SecureRandom = platformSecureRandom() // To minimise diff of CryptoUtils against open-source.
|
||||||
|
@ -21,13 +21,13 @@ import net.corda.core.CordaRuntimeException
|
|||||||
* the exception is handled. This ID is propagated to counterparty flows, even when the [FlowException] is
|
* the exception is handled. This ID is propagated to counterparty flows, even when the [FlowException] is
|
||||||
* downgraded to an [UnexpectedFlowEndException]. This is so the error conditions may be correlated later on.
|
* downgraded to an [UnexpectedFlowEndException]. This is so the error conditions may be correlated later on.
|
||||||
*/
|
*/
|
||||||
open class FlowException(message: String?, cause: Throwable?) :
|
open class FlowException(message: String?, cause: Throwable?, var originalErrorId: Long? = null) :
|
||||||
CordaException(message, cause), IdentifiableException {
|
CordaException(message, cause), IdentifiableException {
|
||||||
|
constructor(message: String?, cause: Throwable?) : this(message, cause, null)
|
||||||
constructor(message: String?) : this(message, null)
|
constructor(message: String?) : this(message, null)
|
||||||
constructor(cause: Throwable?) : this(cause?.toString(), cause)
|
constructor(cause: Throwable?) : this(cause?.toString(), cause)
|
||||||
constructor() : this(null, null)
|
constructor() : this(null, null)
|
||||||
|
|
||||||
var originalErrorId: Long? = null
|
|
||||||
override fun getErrorId(): Long? = originalErrorId
|
override fun getErrorId(): Long? = originalErrorId
|
||||||
}
|
}
|
||||||
// DOCEND 1
|
// DOCEND 1
|
||||||
|
@ -24,22 +24,22 @@ import java.security.cert.X509Certificate
|
|||||||
// also note that IDs are numbered from 1 upwards, matching numbering of other enum types in ASN.1 specifications.
|
// also note that IDs are numbered from 1 upwards, matching numbering of other enum types in ASN.1 specifications.
|
||||||
// TODO: Link to the specification once it has a permanent URL
|
// TODO: Link to the specification once it has a permanent URL
|
||||||
enum class CertRole(val validParents: NonEmptySet<CertRole?>, val isIdentity: Boolean, val isWellKnown: Boolean) : ASN1Encodable {
|
enum class CertRole(val validParents: NonEmptySet<CertRole?>, val isIdentity: Boolean, val isWellKnown: Boolean) : ASN1Encodable {
|
||||||
/** Intermediate CA (Doorman service). */
|
/** Signing certificate for the Doorman CA. */
|
||||||
INTERMEDIATE_CA(NonEmptySet.of(null), false, false),
|
DOORMAN_CA(NonEmptySet.of(null), false, false),
|
||||||
/** Signing certificate for the network map. */
|
/** Signing certificate for the network map. */
|
||||||
NETWORK_MAP(NonEmptySet.of(null), false, false),
|
NETWORK_MAP(NonEmptySet.of(null), false, false),
|
||||||
/** Well known (publicly visible) identity of a service (such as notary). */
|
/** Well known (publicly visible) identity of a service (such as notary). */
|
||||||
SERVICE_IDENTITY(NonEmptySet.of(INTERMEDIATE_CA), true, true),
|
SERVICE_IDENTITY(NonEmptySet.of(DOORMAN_CA), true, true),
|
||||||
/** Node level CA from which the TLS and well known identity certificates are issued. */
|
/** Node level CA from which the TLS and well known identity certificates are issued. */
|
||||||
NODE_CA(NonEmptySet.of(INTERMEDIATE_CA), false, false),
|
NODE_CA(NonEmptySet.of(DOORMAN_CA), false, false),
|
||||||
/** Transport layer security certificate for a node. */
|
/** Transport layer security certificate for a node. */
|
||||||
TLS(NonEmptySet.of(NODE_CA), false, false),
|
TLS(NonEmptySet.of(NODE_CA), false, false),
|
||||||
/** Well known (publicly visible) identity of a legal entity. */
|
/** Well known (publicly visible) identity of a legal entity. */
|
||||||
// TODO: at the moment, Legal Identity certs are issued by Node CA only. However, [INTERMEDIATE_CA] is also added
|
// TODO: at the moment, Legal Identity certs are issued by Node CA only. However, [DOORMAN_CA] is also added
|
||||||
// as a valid parent of [LEGAL_IDENTITY] for backwards compatibility purposes (eg. if we decide TLS has its
|
// as a valid parent of [LEGAL_IDENTITY] for backwards compatibility purposes (eg. if we decide TLS has its
|
||||||
// own Root CA and Intermediate CA directly issues Legal Identities; thus, there won't be a requirement for
|
// own Root CA and Doorman CA directly issues Legal Identities; thus, there won't be a requirement for
|
||||||
// Node CA). Consider removing [INTERMEDIATE_CA] from [validParents] when the model is finalised.
|
// Node CA). Consider removing [DOORMAN_CA] from [validParents] when the model is finalised.
|
||||||
LEGAL_IDENTITY(NonEmptySet.of(INTERMEDIATE_CA, NODE_CA), true, true),
|
LEGAL_IDENTITY(NonEmptySet.of(DOORMAN_CA, NODE_CA), true, true),
|
||||||
/** Confidential (limited visibility) identity of a legal entity. */
|
/** Confidential (limited visibility) identity of a legal entity. */
|
||||||
CONFIDENTIAL_LEGAL_IDENTITY(NonEmptySet.of(LEGAL_IDENTITY), true, false);
|
CONFIDENTIAL_LEGAL_IDENTITY(NonEmptySet.of(LEGAL_IDENTITY), true, false);
|
||||||
|
|
||||||
|
@ -4,17 +4,41 @@ import net.corda.core.crypto.DigitalSignature
|
|||||||
import net.corda.core.crypto.SignedData
|
import net.corda.core.crypto.SignedData
|
||||||
import net.corda.core.crypto.verify
|
import net.corda.core.crypto.verify
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
|
||||||
import net.corda.core.serialization.SerializedBytes
|
import net.corda.core.serialization.SerializedBytes
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.*
|
||||||
|
|
||||||
// TODO: Rename this to DigitalSignature.WithCert once we're happy for it to be public API. The methods will need documentation
|
// TODO: Rename this to DigitalSignature.WithCert once we're happy for it to be public API. The methods will need documentation
|
||||||
// and the correct exceptions will be need to be annotated
|
// and the correct exceptions will be need to be annotated
|
||||||
/** A digital signature with attached certificate of the public key. */
|
/** A digital signature with attached certificate of the public key and (optionally) the remaining chain of the certificates from the certificate path. */
|
||||||
class DigitalSignatureWithCert(val by: X509Certificate, bytes: ByteArray) : DigitalSignature(bytes) {
|
class DigitalSignatureWithCert(val by: X509Certificate, val parentCertsChain: List<X509Certificate>, bytes: ByteArray) : DigitalSignature(bytes) {
|
||||||
|
@DeprecatedConstructorForDeserialization(1)
|
||||||
|
constructor(by: X509Certificate, bytes: ByteArray) : this(by, emptyList(), bytes)
|
||||||
|
|
||||||
|
val fullCertChain: List<X509Certificate> get() = listOf(by) + parentCertsChain
|
||||||
|
val fullCertPath: CertPath get() = CertificateFactory.getInstance("X.509").generateCertPath(fullCertChain)
|
||||||
|
|
||||||
fun verify(content: ByteArray): Boolean = by.publicKey.verify(content, this)
|
fun verify(content: ByteArray): Boolean = by.publicKey.verify(content, this)
|
||||||
fun verify(content: OpaqueBytes): Boolean = verify(content.bytes)
|
fun verify(content: OpaqueBytes): Boolean = verify(content.bytes)
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (parentCertsChain.isNotEmpty()) {
|
||||||
|
val parameters = PKIXParameters(setOf(TrustAnchor(parentCertsChain.last(), null))).apply { isRevocationEnabled = false }
|
||||||
|
try {
|
||||||
|
CertPathValidator.getInstance("PKIX").validate(fullCertPath, parameters)
|
||||||
|
} catch (e: CertPathValidatorException) {
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
"""Cert path failed to validate.
|
||||||
|
Reason: ${e.reason}
|
||||||
|
Offending cert index: ${e.index}
|
||||||
|
Cert path: $fullCertPath
|
||||||
|
""", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Similar to [SignedData] but instead of just attaching the public key, the certificate for the key is attached instead. */
|
/** Similar to [SignedData] but instead of just attaching the public key, the certificate for the key is attached instead. */
|
||||||
|
@ -17,6 +17,7 @@ import net.corda.core.serialization.SerializeAsTokenContext
|
|||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.NonEmptySet
|
import net.corda.core.utilities.NonEmptySet
|
||||||
import net.corda.core.utilities.UntrustworthyData
|
import net.corda.core.utilities.UntrustworthyData
|
||||||
|
import net.corda.core.utilities.debug
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
import java.nio.file.FileAlreadyExistsException
|
import java.nio.file.FileAlreadyExistsException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -75,7 +76,7 @@ sealed class FetchDataFlow<T : NamedByHash, in W : Any>(
|
|||||||
return if (toFetch.isEmpty()) {
|
return if (toFetch.isEmpty()) {
|
||||||
Result(fromDisk, emptyList())
|
Result(fromDisk, emptyList())
|
||||||
} else {
|
} else {
|
||||||
logger.info("Requesting ${toFetch.size} dependency(s) for verification from ${otherSideSession.counterparty.name}")
|
logger.debug { "Requesting ${toFetch.size} dependency(s) for verification from ${otherSideSession.counterparty.name}" }
|
||||||
|
|
||||||
// TODO: Support "large message" response streaming so response sizes are not limited by RAM.
|
// TODO: Support "large message" response streaming so response sizes are not limited by RAM.
|
||||||
// We can then switch to requesting items in large batches to minimise the latency penalty.
|
// We can then switch to requesting items in large batches to minimise the latency penalty.
|
||||||
@ -93,7 +94,7 @@ sealed class FetchDataFlow<T : NamedByHash, in W : Any>(
|
|||||||
}
|
}
|
||||||
// Check for a buggy/malicious peer answering with something that we didn't ask for.
|
// Check for a buggy/malicious peer answering with something that we didn't ask for.
|
||||||
val downloaded = validateFetchResponse(UntrustworthyData(maybeItems), toFetch)
|
val downloaded = validateFetchResponse(UntrustworthyData(maybeItems), toFetch)
|
||||||
logger.info("Fetched ${downloaded.size} elements from ${otherSideSession.counterparty.name}")
|
logger.debug { "Fetched ${downloaded.size} elements from ${otherSideSession.counterparty.name}" }
|
||||||
maybeWriteToDisk(downloaded)
|
maybeWriteToDisk(downloaded)
|
||||||
Result(fromDisk, downloaded)
|
Result(fromDisk, downloaded)
|
||||||
}
|
}
|
||||||
|
@ -4,21 +4,11 @@ package net.corda.core.internal
|
|||||||
|
|
||||||
import net.corda.core.DeleteForDJVM
|
import net.corda.core.DeleteForDJVM
|
||||||
import net.corda.core.KeepForDJVM
|
import net.corda.core.KeepForDJVM
|
||||||
import net.corda.core.cordapp.Cordapp
|
|
||||||
import net.corda.core.cordapp.CordappConfig
|
|
||||||
import net.corda.core.cordapp.CordappContext
|
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.flows.FlowLogic
|
|
||||||
import net.corda.core.node.ServicesForResolution
|
|
||||||
import net.corda.core.serialization.*
|
import net.corda.core.serialization.*
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
|
||||||
import net.corda.core.transactions.SignedTransaction
|
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
|
||||||
import net.corda.core.transactions.WireTransaction
|
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import net.corda.core.utilities.UntrustworthyData
|
import net.corda.core.utilities.UntrustworthyData
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.MDC
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Observer
|
import rx.Observer
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
|
@ -29,12 +29,6 @@ fun <K, V> Caffeine<in K, in V>.buildNamed(name: String): Cache<K, V> {
|
|||||||
return this.build<K, V>()
|
return this.build<K, V>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <K, V> Caffeine<in K, in V>.buildNamed(name: String, loadFunc: (K) -> V): LoadingCache<K, V> {
|
|
||||||
checkCacheName(name)
|
|
||||||
return this.build<K, V>(loadFunc)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun <K, V> Caffeine<in K, in V>.buildNamed(name: String, loader: CacheLoader<K, V>): LoadingCache<K, V> {
|
fun <K, V> Caffeine<in K, in V>.buildNamed(name: String, loader: CacheLoader<K, V>): LoadingCache<K, V> {
|
||||||
checkCacheName(name)
|
checkCacheName(name)
|
||||||
return this.build<K, V>(loader)
|
return this.build<K, V>(loader)
|
||||||
|
@ -24,25 +24,29 @@ data class CordappImpl(
|
|||||||
override val customSchemas: Set<MappedSchema>,
|
override val customSchemas: Set<MappedSchema>,
|
||||||
override val allFlows: List<Class<out FlowLogic<*>>>,
|
override val allFlows: List<Class<out FlowLogic<*>>>,
|
||||||
override val jarPath: URL,
|
override val jarPath: URL,
|
||||||
|
val info: Info,
|
||||||
override val jarHash: SecureHash.SHA256) : Cordapp {
|
override val jarHash: SecureHash.SHA256) : Cordapp {
|
||||||
override val name: String = jarPath.toPath().fileName.toString().removeSuffix(".jar")
|
override val name: String = jarName(jarPath)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun jarName(url: URL): String = url.toPath().fileName.toString().removeSuffix(".jar")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An exhaustive list of all classes relevant to the node within this CorDapp
|
* An exhaustive list of all classes relevant to the node within this CorDapp
|
||||||
*
|
*
|
||||||
* TODO: Also add [SchedulableFlow] as a Cordapp class
|
* TODO: Also add [SchedulableFlow] as a Cordapp class
|
||||||
*/
|
*/
|
||||||
override val cordappClasses = ((rpcFlows + initiatedFlows + services + serializationWhitelists.map { javaClass }).map { it.name } + contractClassNames)
|
override val cordappClasses: List<String> = (rpcFlows + initiatedFlows + services + serializationWhitelists.map { javaClass }).map { it.name } + contractClassNames
|
||||||
|
|
||||||
data class Info(override val shortName: String, override val vendor: String, override val version: String): Cordapp.Info {
|
// TODO Why a seperate Info class and not just have the fields directly in CordappImpl?
|
||||||
|
data class Info(val shortName: String, val vendor: String, val version: String, val minimumPlatformVersion: Int, val targetPlatformVersion: Int) {
|
||||||
companion object {
|
companion object {
|
||||||
private const val UNKNOWN_VALUE = "Unknown"
|
private const val UNKNOWN_VALUE = "Unknown"
|
||||||
|
|
||||||
val UNKNOWN = Info(UNKNOWN_VALUE, UNKNOWN_VALUE, UNKNOWN_VALUE)
|
val UNKNOWN = Info(UNKNOWN_VALUE, UNKNOWN_VALUE, UNKNOWN_VALUE, 1, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hasUnknownFields(): Boolean {
|
fun hasUnknownFields(): Boolean = arrayOf(shortName, vendor, version).any { it == UNKNOWN_VALUE }
|
||||||
return setOf(shortName, vendor, version).any { it == UNKNOWN_VALUE }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
package net.corda.core.internal.cordapp
|
||||||
|
|
||||||
|
import net.corda.core.internal.VisibleForTesting
|
||||||
|
import net.corda.core.utilities.loggerFor
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a way to acquire information about the calling CorDapp.
|
||||||
|
*/
|
||||||
|
object CordappInfoResolver {
|
||||||
|
private val logger = loggerFor<CordappInfoResolver>()
|
||||||
|
private val cordappClasses: ConcurrentHashMap<String, Set<CordappImpl.Info>> = ConcurrentHashMap()
|
||||||
|
|
||||||
|
// TODO use the StackWalker API once we migrate to Java 9+
|
||||||
|
private var cordappInfoResolver: () -> CordappImpl.Info? = {
|
||||||
|
Exception().stackTrace
|
||||||
|
.mapNotNull { cordappClasses[it.className] }
|
||||||
|
// If there is more than one cordapp registered for a class name we can't determine the "correct" one and return null.
|
||||||
|
.firstOrNull { it.size < 2 }?.single()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Associates class names with CorDapps or logs a warning when a CorDapp is already registered for a given class.
|
||||||
|
* This could happen when trying to run different versions of the same CorDapp on the same node.
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
fun register(classes: List<String>, cordapp: CordappImpl.Info) {
|
||||||
|
classes.forEach {
|
||||||
|
if (cordappClasses.containsKey(it)) {
|
||||||
|
logger.warn("More than one CorDapp registered for $it.")
|
||||||
|
cordappClasses[it] = cordappClasses[it]!! + cordapp
|
||||||
|
} else {
|
||||||
|
cordappClasses[it] = setOf(cordapp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This should only be used when making a change that would break compatibility with existing CorDapps. The change
|
||||||
|
* can then be version-gated, meaning the old behaviour is used if the calling CorDapp's target version is lower
|
||||||
|
* than the platform version that introduces the new behaviour.
|
||||||
|
* In situations where a `[CordappProvider]` is available the CorDapp context should be obtained from there.
|
||||||
|
*
|
||||||
|
* @return Information about the CorDapp from which the invoker is called, null if called outside a CorDapp or the
|
||||||
|
* calling CorDapp cannot be reliably determined..
|
||||||
|
*/
|
||||||
|
fun getCorDappInfo(): CordappImpl.Info? = cordappInfoResolver()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporarily switch out the internal resolver for another one. For use in testing.
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
@VisibleForTesting
|
||||||
|
fun withCordappInfoResolution(tempResolver: () -> CordappImpl.Info?, block: () -> Unit) {
|
||||||
|
val resolver = cordappInfoResolver
|
||||||
|
cordappInfoResolver = tempResolver
|
||||||
|
try {
|
||||||
|
block()
|
||||||
|
} finally {
|
||||||
|
cordappInfoResolver = resolver
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
internal fun clear() {
|
||||||
|
cordappClasses.clear()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package net.corda.core.internal.notary
|
||||||
|
|
||||||
|
import net.corda.core.concurrent.CordaFuture
|
||||||
|
import net.corda.core.contracts.StateRef
|
||||||
|
import net.corda.core.contracts.TimeWindow
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.flows.NotarisationRequestSignature
|
||||||
|
import net.corda.core.flows.NotaryError
|
||||||
|
import net.corda.core.identity.Party
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service that records input states of the given transaction and provides conflict information
|
||||||
|
* if any of the inputs have already been used in another transaction.
|
||||||
|
*/
|
||||||
|
interface AsyncUniquenessProvider : UniquenessProvider {
|
||||||
|
/** Commits all input states of the given transaction. */
|
||||||
|
fun commitAsync(states: List<StateRef>, txId: SecureHash, callerIdentity: Party, requestSignature: NotarisationRequestSignature, timeWindow: TimeWindow?, references: List<StateRef>): CordaFuture<Result>
|
||||||
|
|
||||||
|
/** Commits all input states of the given transaction synchronously. Use [commitAsync] for better performance. */
|
||||||
|
override fun commit(states: List<StateRef>, txId: SecureHash, callerIdentity: Party, requestSignature: NotarisationRequestSignature, timeWindow: TimeWindow?, references: List<StateRef>) {
|
||||||
|
val result = commitAsync(states, txId, callerIdentity, requestSignature, timeWindow,references).get()
|
||||||
|
if (result is Result.Failure) {
|
||||||
|
throw NotaryInternalException(result.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The outcome of committing a transaction. */
|
||||||
|
sealed class Result {
|
||||||
|
/** Indicates that all input states have been committed successfully. */
|
||||||
|
object Success : Result()
|
||||||
|
/** Indicates that the transaction has not been committed. */
|
||||||
|
data class Failure(val error: NotaryError) : Result()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -67,4 +67,4 @@ abstract class TrustedAuthorityNotaryService : NotaryService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Sign multiple transactions at once by building their Merkle tree and then signing over its root.
|
// TODO: Sign multiple transactions at once by building their Merkle tree and then signing over its root.
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ import net.corda.core.serialization.CordaSerializable
|
|||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.Try
|
import net.corda.core.utilities.Try
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.subjects.PublishSubject
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
@ -42,7 +41,8 @@ data class StateMachineInfo @JvmOverloads constructor(
|
|||||||
* An object representing information about the initiator of the flow. Note that this field is
|
* An object representing information about the initiator of the flow. Note that this field is
|
||||||
* superseded by the [invocationContext] property, which has more detail.
|
* superseded by the [invocationContext] property, which has more detail.
|
||||||
*/
|
*/
|
||||||
@Deprecated("There is more info available using 'context'") val initiator: FlowInitiator,
|
@Deprecated("There is more info available using 'invocationContext'")
|
||||||
|
val initiator: FlowInitiator,
|
||||||
/** A [DataFeed] of the current progress step as a human readable string, and updates to that string. */
|
/** A [DataFeed] of the current progress step as a human readable string, and updates to that string. */
|
||||||
val progressTrackerStepAndUpdates: DataFeed<String, String>?,
|
val progressTrackerStepAndUpdates: DataFeed<String, String>?,
|
||||||
/** An [InvocationContext] describing why and by whom the flow was started. */
|
/** An [InvocationContext] describing why and by whom the flow was started. */
|
||||||
@ -76,7 +76,8 @@ sealed class StateMachineUpdate {
|
|||||||
// DOCSTART 1
|
// DOCSTART 1
|
||||||
/**
|
/**
|
||||||
* Data class containing information about the scheduled network parameters update. The info is emitted every time node
|
* Data class containing information about the scheduled network parameters update. The info is emitted every time node
|
||||||
* receives network map with [ParametersUpdate] which wasn't seen before. For more information see: [CordaRPCOps.networkParametersFeed] and [CordaRPCOps.acceptNewNetworkParameters].
|
* receives network map with [ParametersUpdate] which wasn't seen before. For more information see: [CordaRPCOps.networkParametersFeed]
|
||||||
|
* and [CordaRPCOps.acceptNewNetworkParameters].
|
||||||
* @property hash new [NetworkParameters] hash
|
* @property hash new [NetworkParameters] hash
|
||||||
* @property parameters new [NetworkParameters] data structure
|
* @property parameters new [NetworkParameters] data structure
|
||||||
* @property description description of the update
|
* @property description description of the update
|
||||||
@ -96,12 +97,6 @@ data class StateMachineTransactionMapping(val stateMachineRunId: StateMachineRun
|
|||||||
|
|
||||||
/** RPC operations that the node exposes to clients. */
|
/** RPC operations that the node exposes to clients. */
|
||||||
interface CordaRPCOps : RPCOps {
|
interface CordaRPCOps : RPCOps {
|
||||||
/**
|
|
||||||
* Returns the RPC protocol version, which is the same the node's Platform Version. Exists since version 1 so guaranteed
|
|
||||||
* to be present.
|
|
||||||
*/
|
|
||||||
override val protocolVersion: Int get() = nodeInfo().platformVersion
|
|
||||||
|
|
||||||
/** Returns a list of currently in-progress state machine infos. */
|
/** Returns a list of currently in-progress state machine infos. */
|
||||||
fun stateMachinesSnapshot(): List<StateMachineInfo>
|
fun stateMachinesSnapshot(): List<StateMachineInfo>
|
||||||
|
|
||||||
@ -233,6 +228,9 @@ interface CordaRPCOps : RPCOps {
|
|||||||
@RPCReturnsObservables
|
@RPCReturnsObservables
|
||||||
fun networkMapFeed(): DataFeed<List<NodeInfo>, NetworkMapCache.MapChange>
|
fun networkMapFeed(): DataFeed<List<NodeInfo>, NetworkMapCache.MapChange>
|
||||||
|
|
||||||
|
/** Returns the network parameters the node is operating under. */
|
||||||
|
val networkParameters: NetworkParameters
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns [DataFeed] object containing information on currently scheduled parameters update (null if none are currently scheduled)
|
* Returns [DataFeed] object containing information on currently scheduled parameters update (null if none are currently scheduled)
|
||||||
* and observable with future update events. Any update that occurs before the deadline automatically cancels the current one.
|
* and observable with future update events. Any update that occurs before the deadline automatically cancels the current one.
|
||||||
@ -406,38 +404,20 @@ interface CordaRPCOps : RPCOps {
|
|||||||
* This does not wait for flows to be completed.
|
* This does not wait for flows to be completed.
|
||||||
*/
|
*/
|
||||||
fun shutdown()
|
fun shutdown()
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a [DataFeed] that keeps track on the count of pending flows.
|
* Shuts the node down. Returns immediately.
|
||||||
*/
|
* @param drainPendingFlows whether the node will wait for pending flows to be completed before exiting. While draining, new flows from RPC will be rejected.
|
||||||
fun CordaRPCOps.pendingFlowsCount(): DataFeed<Int, Pair<Int, Int>> {
|
*/
|
||||||
|
fun terminate(drainPendingFlows: Boolean = false)
|
||||||
|
|
||||||
val stateMachineState = stateMachinesFeed()
|
/**
|
||||||
var pendingFlowsCount = stateMachineState.snapshot.size
|
* Returns whether the node is waiting for pending flows to complete before shutting down.
|
||||||
var completedFlowsCount = 0
|
* Disabling draining mode cancels this state.
|
||||||
val updates = PublishSubject.create<Pair<Int, Int>>()
|
*
|
||||||
stateMachineState
|
* @return whether the node will shutdown when the pending flows count reaches zero.
|
||||||
.updates
|
*/
|
||||||
.doOnNext { update ->
|
fun isWaitingForShutdown(): Boolean
|
||||||
when (update) {
|
|
||||||
is StateMachineUpdate.Added -> {
|
|
||||||
pendingFlowsCount++
|
|
||||||
updates.onNext(completedFlowsCount to pendingFlowsCount)
|
|
||||||
}
|
|
||||||
is StateMachineUpdate.Removed -> {
|
|
||||||
completedFlowsCount++
|
|
||||||
updates.onNext(completedFlowsCount to pendingFlowsCount)
|
|
||||||
if (completedFlowsCount == pendingFlowsCount) {
|
|
||||||
updates.onCompleted()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.subscribe()
|
|
||||||
if (completedFlowsCount == 0) {
|
|
||||||
updates.onCompleted()
|
|
||||||
}
|
|
||||||
return DataFeed(pendingFlowsCount, updates)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T : ContractState> CordaRPCOps.vaultQueryBy(criteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(),
|
inline fun <reified T : ContractState> CordaRPCOps.vaultQueryBy(criteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(),
|
||||||
|
@ -28,4 +28,4 @@ interface AppServiceHub : ServiceHub {
|
|||||||
* TODO it is assumed here that the flow object has an appropriate classloader.
|
* TODO it is assumed here that the flow object has an appropriate classloader.
|
||||||
*/
|
*/
|
||||||
fun <T> startTrackedFlow(flow: FlowLogic<T>): FlowProgressHandle<T>
|
fun <T> startTrackedFlow(flow: FlowLogic<T>): FlowProgressHandle<T>
|
||||||
}
|
}
|
||||||
|
@ -140,4 +140,4 @@ interface IdentityService {
|
|||||||
fun partiesFromName(query: String, exactMatch: Boolean): Set<Party>
|
fun partiesFromName(query: String, exactMatch: Boolean): Set<Party>
|
||||||
}
|
}
|
||||||
|
|
||||||
class UnknownAnonymousPartyException(msg: String) : CordaException(msg)
|
class UnknownAnonymousPartyException(message: String) : CordaException(message)
|
||||||
|
@ -160,7 +160,7 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
|
|||||||
val notary: AbstractParty?,
|
val notary: AbstractParty?,
|
||||||
val lockId: String?,
|
val lockId: String?,
|
||||||
val lockUpdateTime: Instant?,
|
val lockUpdateTime: Instant?,
|
||||||
val isRelevant: Vault.RelevancyStatus?
|
val relevancyStatus: Vault.RelevancyStatus?
|
||||||
) {
|
) {
|
||||||
constructor(ref: StateRef,
|
constructor(ref: StateRef,
|
||||||
contractStateClassName: String,
|
contractStateClassName: String,
|
||||||
|
@ -73,7 +73,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
|
|||||||
|
|
||||||
abstract class CommonQueryCriteria : QueryCriteria() {
|
abstract class CommonQueryCriteria : QueryCriteria() {
|
||||||
abstract val status: Vault.StateStatus
|
abstract val status: Vault.StateStatus
|
||||||
open val isRelevant: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
open val relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||||
abstract val contractStateTypes: Set<Class<out ContractState>>?
|
abstract val contractStateTypes: Set<Class<out ContractState>>?
|
||||||
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
|
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
|
||||||
return parser.parseCriteria(this)
|
return parser.parseCriteria(this)
|
||||||
@ -90,7 +90,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
|
|||||||
val notary: List<AbstractParty>? = null,
|
val notary: List<AbstractParty>? = null,
|
||||||
val softLockingCondition: SoftLockingCondition? = null,
|
val softLockingCondition: SoftLockingCondition? = null,
|
||||||
val timeCondition: TimeCondition? = null,
|
val timeCondition: TimeCondition? = null,
|
||||||
override val isRelevant: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
override val relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||||
) : CommonQueryCriteria() {
|
) : CommonQueryCriteria() {
|
||||||
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
|
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
|
||||||
super.visit(parser)
|
super.visit(parser)
|
||||||
@ -125,15 +125,15 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
|
|||||||
val externalId: List<String>? = null,
|
val externalId: List<String>? = null,
|
||||||
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
|
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
|
||||||
override val contractStateTypes: Set<Class<out ContractState>>? = null,
|
override val contractStateTypes: Set<Class<out ContractState>>? = null,
|
||||||
override val isRelevant: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
override val relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||||
) : CommonQueryCriteria() {
|
) : CommonQueryCriteria() {
|
||||||
constructor(
|
constructor(
|
||||||
participants: List<AbstractParty>? = null,
|
participants: List<AbstractParty>? = null,
|
||||||
linearId: List<UniqueIdentifier>? = null,
|
linearId: List<UniqueIdentifier>? = null,
|
||||||
status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
|
status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
|
||||||
contractStateTypes: Set<Class<out ContractState>>? = null,
|
contractStateTypes: Set<Class<out ContractState>>? = null,
|
||||||
isRelevant: Vault.RelevancyStatus
|
relevancyStatus: Vault.RelevancyStatus
|
||||||
) : this(participants, linearId?.map { it.id }, linearId?.mapNotNull { it.externalId }, status, contractStateTypes, isRelevant)
|
) : this(participants, linearId?.map { it.id }, linearId?.mapNotNull { it.externalId }, status, contractStateTypes, relevancyStatus)
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
participants: List<AbstractParty>? = null,
|
participants: List<AbstractParty>? = null,
|
||||||
@ -175,7 +175,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
|
|||||||
val issuerRef: List<OpaqueBytes>? = null,
|
val issuerRef: List<OpaqueBytes>? = null,
|
||||||
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
|
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
|
||||||
override val contractStateTypes: Set<Class<out ContractState>>? = null,
|
override val contractStateTypes: Set<Class<out ContractState>>? = null,
|
||||||
override val isRelevant: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
override val relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||||
) : CommonQueryCriteria() {
|
) : CommonQueryCriteria() {
|
||||||
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
|
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
|
||||||
super.visit(parser)
|
super.visit(parser)
|
||||||
@ -215,7 +215,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
|
|||||||
val expression: CriteriaExpression<L, Boolean>,
|
val expression: CriteriaExpression<L, Boolean>,
|
||||||
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
|
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
|
||||||
override val contractStateTypes: Set<Class<out ContractState>>? = null,
|
override val contractStateTypes: Set<Class<out ContractState>>? = null,
|
||||||
override val isRelevant: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
override val relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||||
) : CommonQueryCriteria() {
|
) : CommonQueryCriteria() {
|
||||||
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
|
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
|
||||||
super.visit(parser)
|
super.visit(parser)
|
||||||
|
@ -5,6 +5,10 @@ package net.corda.core.node.services.vault
|
|||||||
import net.corda.core.DoNotImplement
|
import net.corda.core.DoNotImplement
|
||||||
import net.corda.core.internal.declaredField
|
import net.corda.core.internal.declaredField
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
|
import net.corda.core.node.services.vault.CollectionOperator.*
|
||||||
|
import net.corda.core.node.services.vault.ColumnPredicate.*
|
||||||
|
import net.corda.core.node.services.vault.EqualityComparisonOperator.*
|
||||||
|
import net.corda.core.node.services.vault.LikenessOperator.*
|
||||||
import net.corda.core.schemas.PersistentState
|
import net.corda.core.schemas.PersistentState
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import java.lang.reflect.Field
|
import java.lang.reflect.Field
|
||||||
@ -24,7 +28,9 @@ enum class BinaryLogicalOperator : Operator {
|
|||||||
|
|
||||||
enum class EqualityComparisonOperator : Operator {
|
enum class EqualityComparisonOperator : Operator {
|
||||||
EQUAL,
|
EQUAL,
|
||||||
NOT_EQUAL
|
NOT_EQUAL,
|
||||||
|
EQUAL_IGNORE_CASE,
|
||||||
|
NOT_EQUAL_IGNORE_CASE
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class BinaryComparisonOperator : Operator {
|
enum class BinaryComparisonOperator : Operator {
|
||||||
@ -41,12 +47,16 @@ enum class NullOperator : Operator {
|
|||||||
|
|
||||||
enum class LikenessOperator : Operator {
|
enum class LikenessOperator : Operator {
|
||||||
LIKE,
|
LIKE,
|
||||||
NOT_LIKE
|
NOT_LIKE,
|
||||||
|
LIKE_IGNORE_CASE,
|
||||||
|
NOT_LIKE_IGNORE_CASE
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class CollectionOperator : Operator {
|
enum class CollectionOperator : Operator {
|
||||||
IN,
|
IN,
|
||||||
NOT_IN
|
NOT_IN,
|
||||||
|
IN_IGNORE_CASE,
|
||||||
|
NOT_IN_IGNORE_CASE
|
||||||
}
|
}
|
||||||
|
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
@ -251,27 +261,45 @@ object Builder {
|
|||||||
fun <R : Comparable<R>> Field.comparePredicate(operator: BinaryComparisonOperator, value: R) = info().comparePredicate(operator, value)
|
fun <R : Comparable<R>> Field.comparePredicate(operator: BinaryComparisonOperator, value: R) = info().comparePredicate(operator, value)
|
||||||
fun <R : Comparable<R>> FieldInfo.comparePredicate(operator: BinaryComparisonOperator, value: R) = predicate(compare(operator, value))
|
fun <R : Comparable<R>> FieldInfo.comparePredicate(operator: BinaryComparisonOperator, value: R) = predicate(compare(operator, value))
|
||||||
|
|
||||||
fun <O, R> KProperty1<O, R?>.equal(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.EQUAL, value))
|
@JvmOverloads
|
||||||
fun <O, R> KProperty1<O, R?>.notEqual(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.NOT_EQUAL, value))
|
fun <O, R> KProperty1<O, R?>.equal(value: R, exactMatch: Boolean = true) = predicate(Builder.equal(value, exactMatch))
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun <O, R> KProperty1<O, R?>.notEqual(value: R, exactMatch: Boolean = true) = predicate(Builder.notEqual(value, exactMatch))
|
||||||
|
|
||||||
fun <O, R : Comparable<R>> KProperty1<O, R?>.lessThan(value: R) = comparePredicate(BinaryComparisonOperator.LESS_THAN, value)
|
fun <O, R : Comparable<R>> KProperty1<O, R?>.lessThan(value: R) = comparePredicate(BinaryComparisonOperator.LESS_THAN, value)
|
||||||
|
|
||||||
fun <O, R : Comparable<R>> KProperty1<O, R?>.lessThanOrEqual(value: R) = comparePredicate(BinaryComparisonOperator.LESS_THAN_OR_EQUAL, value)
|
fun <O, R : Comparable<R>> KProperty1<O, R?>.lessThanOrEqual(value: R) = comparePredicate(BinaryComparisonOperator.LESS_THAN_OR_EQUAL, value)
|
||||||
|
|
||||||
fun <O, R : Comparable<R>> KProperty1<O, R?>.greaterThan(value: R) = comparePredicate(BinaryComparisonOperator.GREATER_THAN, value)
|
fun <O, R : Comparable<R>> KProperty1<O, R?>.greaterThan(value: R) = comparePredicate(BinaryComparisonOperator.GREATER_THAN, value)
|
||||||
|
|
||||||
fun <O, R : Comparable<R>> KProperty1<O, R?>.greaterThanOrEqual(value: R) = comparePredicate(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL, value)
|
fun <O, R : Comparable<R>> KProperty1<O, R?>.greaterThanOrEqual(value: R) = comparePredicate(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL, value)
|
||||||
|
|
||||||
fun <O, R : Comparable<R>> KProperty1<O, R?>.between(from: R, to: R) = predicate(ColumnPredicate.Between(from, to))
|
fun <O, R : Comparable<R>> KProperty1<O, R?>.between(from: R, to: R) = predicate(ColumnPredicate.Between(from, to))
|
||||||
fun <O, R : Comparable<R>> KProperty1<O, R?>.`in`(collection: Collection<R>) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.IN, collection))
|
|
||||||
fun <O, R : Comparable<R>> KProperty1<O, R?>.notIn(collection: Collection<R>) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.NOT_IN, collection))
|
@JvmOverloads
|
||||||
|
fun <O, R : Comparable<R>> KProperty1<O, R?>.`in`(collection: Collection<R>, exactMatch: Boolean = true) = predicate(Builder.`in`(collection, exactMatch))
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun <O, R : Comparable<R>> KProperty1<O, R?>.notIn(collection: Collection<R>, exactMatch: Boolean = true) = predicate(Builder.notIn(collection, exactMatch))
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
||||||
fun <R> Field.equal(value: R) = info().equal(value)
|
fun <R> Field.equal(value: R, exactMatch: Boolean = true) = info().equal(value, exactMatch)
|
||||||
@JvmStatic
|
|
||||||
fun <R> FieldInfo.equal(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.EQUAL, value))
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
@JvmOverloads
|
||||||
fun <R> Field.notEqual(value: R) = info().notEqual(value)
|
fun <R> FieldInfo.equal(value: R, exactMatch: Boolean = true) = predicate(Builder.equal(value, exactMatch))
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun <R> FieldInfo.notEqual(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.NOT_EQUAL, value))
|
@JvmOverloads
|
||||||
|
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
||||||
|
fun <R> Field.notEqual(value: R, exactMatch: Boolean = true) = info().notEqual(value, exactMatch)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
|
fun <R> FieldInfo.notEqual(value: R, exactMatch: Boolean = true) = predicate(Builder.equal(value, exactMatch))
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
||||||
@ -304,44 +332,77 @@ object Builder {
|
|||||||
fun <R : Comparable<R>> FieldInfo.between(from: R, to: R) = predicate(ColumnPredicate.Between(from, to))
|
fun <R : Comparable<R>> FieldInfo.between(from: R, to: R) = predicate(ColumnPredicate.Between(from, to))
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
||||||
fun <R : Comparable<R>> Field.`in`(collection: Collection<R>) = info().`in`(collection)
|
fun <R : Comparable<R>> Field.`in`(collection: Collection<R>, exactMatch: Boolean = true) = info().`in`(collection, exactMatch)
|
||||||
fun <R : Comparable<R>> FieldInfo.`in`(collection: Collection<R>) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.IN, collection))
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
@JvmOverloads
|
||||||
fun <R : Comparable<R>> Field.notIn(collection: Collection<R>) = info().notIn(collection)
|
fun <R : Comparable<R>> FieldInfo.`in`(collection: Collection<R>, exactMatch: Boolean = true) = predicate(Builder.`in`(collection, exactMatch))
|
||||||
@JvmStatic
|
|
||||||
fun <R : Comparable<R>> FieldInfo.notIn(collection: Collection<R>) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.NOT_IN, collection))
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
|
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
||||||
|
fun <R : Comparable<R>> Field.notIn(collection: Collection<R>, exactMatch: Boolean = true) = info().notIn(collection, exactMatch)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
|
fun <R : Comparable<R>> FieldInfo.notIn(collection: Collection<R>, exactMatch: Boolean = true) = predicate(Builder.notIn(collection, exactMatch))
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun <R> equal(value: R, exactMatch: Boolean = true) = EqualityComparison(if (exactMatch) EQUAL else EQUAL_IGNORE_CASE, value)
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun <R> notEqual(value: R, exactMatch: Boolean = true) = EqualityComparison(if (exactMatch) NOT_EQUAL else NOT_EQUAL_IGNORE_CASE, value)
|
||||||
|
|
||||||
fun <R> equal(value: R) = ColumnPredicate.EqualityComparison(EqualityComparisonOperator.EQUAL, value)
|
|
||||||
fun <R> notEqual(value: R) = ColumnPredicate.EqualityComparison(EqualityComparisonOperator.NOT_EQUAL, value)
|
|
||||||
fun <R : Comparable<R>> lessThan(value: R) = compare(BinaryComparisonOperator.LESS_THAN, value)
|
fun <R : Comparable<R>> lessThan(value: R) = compare(BinaryComparisonOperator.LESS_THAN, value)
|
||||||
|
|
||||||
fun <R : Comparable<R>> lessThanOrEqual(value: R) = compare(BinaryComparisonOperator.LESS_THAN_OR_EQUAL, value)
|
fun <R : Comparable<R>> lessThanOrEqual(value: R) = compare(BinaryComparisonOperator.LESS_THAN_OR_EQUAL, value)
|
||||||
|
|
||||||
fun <R : Comparable<R>> greaterThan(value: R) = compare(BinaryComparisonOperator.GREATER_THAN, value)
|
fun <R : Comparable<R>> greaterThan(value: R) = compare(BinaryComparisonOperator.GREATER_THAN, value)
|
||||||
|
|
||||||
fun <R : Comparable<R>> greaterThanOrEqual(value: R) = compare(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL, value)
|
fun <R : Comparable<R>> greaterThanOrEqual(value: R) = compare(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL, value)
|
||||||
|
|
||||||
fun <R : Comparable<R>> between(from: R, to: R) = ColumnPredicate.Between(from, to)
|
fun <R : Comparable<R>> between(from: R, to: R) = ColumnPredicate.Between(from, to)
|
||||||
fun <R : Comparable<R>> `in`(collection: Collection<R>) = ColumnPredicate.CollectionExpression(CollectionOperator.IN, collection)
|
|
||||||
fun <R : Comparable<R>> notIn(collection: Collection<R>) = ColumnPredicate.CollectionExpression(CollectionOperator.NOT_IN, collection)
|
@JvmOverloads
|
||||||
fun like(string: String) = ColumnPredicate.Likeness(LikenessOperator.LIKE, string)
|
fun <R : Comparable<R>> `in`(collection: Collection<R>, exactMatch: Boolean = true) = CollectionExpression(if (exactMatch) IN else IN_IGNORE_CASE, collection)
|
||||||
fun notLike(string: String) = ColumnPredicate.Likeness(LikenessOperator.NOT_LIKE, string)
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun <R : Comparable<R>> notIn(collection: Collection<R>, exactMatch: Boolean = true) = CollectionExpression(if (exactMatch) NOT_IN else NOT_IN_IGNORE_CASE, collection)
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun like(string: String, exactMatch: Boolean = true) = Likeness(if (exactMatch) LIKE else LIKE_IGNORE_CASE, string)
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun notLike(string: String, exactMatch: Boolean = true) = Likeness(if (exactMatch) NOT_LIKE else NOT_LIKE_IGNORE_CASE, string)
|
||||||
|
|
||||||
fun <R> isNull() = ColumnPredicate.NullExpression<R>(NullOperator.IS_NULL)
|
fun <R> isNull() = ColumnPredicate.NullExpression<R>(NullOperator.IS_NULL)
|
||||||
fun <R> isNotNull() = ColumnPredicate.NullExpression<R>(NullOperator.NOT_NULL)
|
fun <R> isNotNull() = ColumnPredicate.NullExpression<R>(NullOperator.NOT_NULL)
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun <O> KProperty1<O, String?>.like(string: String, exactMatch: Boolean = true) = predicate(Builder.like(string, exactMatch))
|
||||||
|
|
||||||
fun <O> KProperty1<O, String?>.like(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.LIKE, string))
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
||||||
fun Field.like(string: String) = info().like(string)
|
fun Field.like(string: String, exactMatch: Boolean = true) = info().like(string, exactMatch)
|
||||||
@JvmStatic
|
|
||||||
fun FieldInfo.like(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.LIKE, string))
|
|
||||||
|
|
||||||
fun <O> KProperty1<O, String?>.notLike(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.NOT_LIKE, string))
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
|
fun FieldInfo.like(string: String, exactMatch: Boolean = true) = predicate(Builder.like(string, exactMatch))
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun <O> KProperty1<O, String?>.notLike(string: String, exactMatch: Boolean = true) = predicate(Builder.notLike(string, exactMatch))
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
||||||
fun Field.notLike(string: String) = info().notLike(string)
|
fun Field.notLike(string: String, exactMatch: Boolean = true) = info().notLike(string, exactMatch)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun FieldInfo.notLike(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.NOT_LIKE, string))
|
@JvmOverloads
|
||||||
|
fun FieldInfo.notLike(string: String, exactMatch: Boolean = true) = predicate(Builder.notLike(string, exactMatch))
|
||||||
|
|
||||||
fun <O, R> KProperty1<O, R?>.isNull() = predicate(ColumnPredicate.NullExpression(NullOperator.IS_NULL))
|
fun <O, R> KProperty1<O, R?>.isNull() = predicate(ColumnPredicate.NullExpression(NullOperator.IS_NULL))
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@ -207,7 +207,13 @@ interface SerializationContext {
|
|||||||
* The use case that we are serializing for, since it influences the implementations chosen.
|
* The use case that we are serializing for, since it influences the implementations chosen.
|
||||||
*/
|
*/
|
||||||
@KeepForDJVM
|
@KeepForDJVM
|
||||||
enum class UseCase { P2P, RPCServer, RPCClient, Storage, Checkpoint, Testing }
|
enum class UseCase {
|
||||||
|
P2P,
|
||||||
|
RPCServer,
|
||||||
|
RPCClient,
|
||||||
|
Storage,
|
||||||
|
Testing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -230,7 +236,6 @@ object SerializationDefaults {
|
|||||||
@DeleteForDJVM val RPC_SERVER_CONTEXT get() = effectiveSerializationEnv.rpcServerContext
|
@DeleteForDJVM val RPC_SERVER_CONTEXT get() = effectiveSerializationEnv.rpcServerContext
|
||||||
@DeleteForDJVM val RPC_CLIENT_CONTEXT get() = effectiveSerializationEnv.rpcClientContext
|
@DeleteForDJVM val RPC_CLIENT_CONTEXT get() = effectiveSerializationEnv.rpcClientContext
|
||||||
@DeleteForDJVM val STORAGE_CONTEXT get() = effectiveSerializationEnv.storageContext
|
@DeleteForDJVM val STORAGE_CONTEXT get() = effectiveSerializationEnv.storageContext
|
||||||
@DeleteForDJVM val CHECKPOINT_CONTEXT get() = effectiveSerializationEnv.checkpointContext
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,198 @@
|
|||||||
|
package net.corda.core.serialization.internal
|
||||||
|
|
||||||
|
import net.corda.core.DeleteForDJVM
|
||||||
|
import net.corda.core.DoNotImplement
|
||||||
|
import net.corda.core.KeepForDJVM
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.serialization.*
|
||||||
|
import net.corda.core.utilities.ByteSequence
|
||||||
|
import net.corda.core.utilities.sequence
|
||||||
|
import java.io.NotSerializableException
|
||||||
|
|
||||||
|
|
||||||
|
object CheckpointSerializationDefaults {
|
||||||
|
@DeleteForDJVM
|
||||||
|
val CHECKPOINT_CONTEXT get() = effectiveSerializationEnv.checkpointContext
|
||||||
|
val CHECKPOINT_SERIALIZATION_FACTORY get() = effectiveSerializationEnv.checkpointSerializationFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for serializing and deserializing objects at checkpoints, using Kryo serialization.
|
||||||
|
*/
|
||||||
|
@KeepForDJVM
|
||||||
|
class CheckpointSerializationFactory(
|
||||||
|
private val scheme: CheckpointSerializationScheme
|
||||||
|
) {
|
||||||
|
|
||||||
|
val defaultContext: CheckpointSerializationContext get() = _currentContext.get() ?: effectiveSerializationEnv.checkpointContext
|
||||||
|
|
||||||
|
private val creator: List<StackTraceElement> = Exception().stackTrace.asList()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize the bytes in to an object, using the prefixed bytes to determine the format.
|
||||||
|
*
|
||||||
|
* @param byteSequence The bytes to deserialize, including a format header prefix.
|
||||||
|
* @param clazz The class or superclass or the object to be deserialized, or [Any] or [Object] if unknown.
|
||||||
|
* @param context A context that configures various parameters to deserialization.
|
||||||
|
*/
|
||||||
|
fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: CheckpointSerializationContext): T {
|
||||||
|
return withCurrentContext(context) { scheme.deserialize(byteSequence, clazz, context) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize an object to bytes using the preferred serialization format version from the context.
|
||||||
|
*
|
||||||
|
* @param obj The object to be serialized.
|
||||||
|
* @param context A context that configures various parameters to serialization, including the serialization format version.
|
||||||
|
*/
|
||||||
|
fun <T : Any> serialize(obj: T, context: CheckpointSerializationContext): SerializedBytes<T> {
|
||||||
|
return withCurrentContext(context) { scheme.serialize(obj, context) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "${this.javaClass.name} scheme=$scheme ${creator.joinToString("\n")}"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return other is CheckpointSerializationFactory && other.scheme == this.scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int = scheme.hashCode()
|
||||||
|
|
||||||
|
private val _currentContext = ThreadLocal<CheckpointSerializationContext?>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the current context inside the block to that supplied.
|
||||||
|
*/
|
||||||
|
fun <T> withCurrentContext(context: CheckpointSerializationContext?, block: () -> T): T {
|
||||||
|
val priorContext = _currentContext.get()
|
||||||
|
if (context != null) _currentContext.set(context)
|
||||||
|
try {
|
||||||
|
return block()
|
||||||
|
} finally {
|
||||||
|
if (context != null) _currentContext.set(priorContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val defaultFactory: CheckpointSerializationFactory get() = effectiveSerializationEnv.checkpointSerializationFactory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@KeepForDJVM
|
||||||
|
@DoNotImplement
|
||||||
|
interface CheckpointSerializationScheme {
|
||||||
|
@Throws(NotSerializableException::class)
|
||||||
|
fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: CheckpointSerializationContext): T
|
||||||
|
|
||||||
|
@Throws(NotSerializableException::class)
|
||||||
|
fun <T : Any> serialize(obj: T, context: CheckpointSerializationContext): SerializedBytes<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters to checkpoint serialization and deserialization.
|
||||||
|
*/
|
||||||
|
@KeepForDJVM
|
||||||
|
@DoNotImplement
|
||||||
|
interface CheckpointSerializationContext {
|
||||||
|
/**
|
||||||
|
* If non-null, apply this encoding (typically compression) when serializing.
|
||||||
|
*/
|
||||||
|
val encoding: SerializationEncoding?
|
||||||
|
/**
|
||||||
|
* The class loader to use for deserialization.
|
||||||
|
*/
|
||||||
|
val deserializationClassLoader: ClassLoader
|
||||||
|
/**
|
||||||
|
* A whitelist that contains (mostly for security purposes) which classes can be serialized and deserialized.
|
||||||
|
*/
|
||||||
|
val whitelist: ClassWhitelist
|
||||||
|
/**
|
||||||
|
* A whitelist that determines (mostly for security purposes) whether a particular encoding may be used when deserializing.
|
||||||
|
*/
|
||||||
|
val encodingWhitelist: EncodingWhitelist
|
||||||
|
/**
|
||||||
|
* A map of any addition properties specific to the particular use case.
|
||||||
|
*/
|
||||||
|
val properties: Map<Any, Any>
|
||||||
|
/**
|
||||||
|
* Duplicate references to the same object preserved in the wire format and when deserialized when this is true,
|
||||||
|
* otherwise they appear as new copies of the object.
|
||||||
|
*/
|
||||||
|
val objectReferencesEnabled: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to return a new context based on this context with the property added.
|
||||||
|
*/
|
||||||
|
fun withProperty(property: Any, value: Any): CheckpointSerializationContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to return a new context based on this context with object references disabled.
|
||||||
|
*/
|
||||||
|
fun withoutReferences(): CheckpointSerializationContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to return a new context based on this context with the deserialization class loader changed.
|
||||||
|
*/
|
||||||
|
fun withClassLoader(classLoader: ClassLoader): CheckpointSerializationContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to return a new context based on this context with the appropriate class loader constructed from the passed attachment identifiers.
|
||||||
|
* (Requires the attachment storage to have been enabled).
|
||||||
|
*/
|
||||||
|
@Throws(MissingAttachmentsException::class)
|
||||||
|
fun withAttachmentsClassLoader(attachmentHashes: List<SecureHash>): CheckpointSerializationContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to return a new context based on this context with the given class specifically whitelisted.
|
||||||
|
*/
|
||||||
|
fun withWhitelisted(clazz: Class<*>): CheckpointSerializationContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A shallow copy of this context but with the given (possibly null) encoding.
|
||||||
|
*/
|
||||||
|
fun withEncoding(encoding: SerializationEncoding?): CheckpointSerializationContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A shallow copy of this context but with the given encoding whitelist.
|
||||||
|
*/
|
||||||
|
fun withEncodingWhitelist(encodingWhitelist: EncodingWhitelist): CheckpointSerializationContext
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The following extension methods are disambiguated from the AMQP-serialization methods by requiring that an
|
||||||
|
* explicit [CheckpointSerializationContext] parameter be provided.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convenience extension method for deserializing a ByteSequence, utilising the default factory.
|
||||||
|
*/
|
||||||
|
inline fun <reified T : Any> ByteSequence.checkpointDeserialize(serializationFactory: CheckpointSerializationFactory = CheckpointSerializationFactory.defaultFactory,
|
||||||
|
context: CheckpointSerializationContext): T {
|
||||||
|
return serializationFactory.deserialize(this, T::class.java, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience extension method for deserializing SerializedBytes with type matching, utilising the default factory.
|
||||||
|
*/
|
||||||
|
inline fun <reified T : Any> SerializedBytes<T>.checkpointDeserialize(serializationFactory: CheckpointSerializationFactory = CheckpointSerializationFactory.defaultFactory,
|
||||||
|
context: CheckpointSerializationContext): T {
|
||||||
|
return serializationFactory.deserialize(this, T::class.java, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience extension method for deserializing a ByteArray, utilising the default factory.
|
||||||
|
*/
|
||||||
|
inline fun <reified T : Any> ByteArray.checkpointDeserialize(serializationFactory: CheckpointSerializationFactory = CheckpointSerializationFactory.defaultFactory,
|
||||||
|
context: CheckpointSerializationContext): T {
|
||||||
|
require(isNotEmpty()) { "Empty bytes" }
|
||||||
|
return this.sequence().checkpointDeserialize(serializationFactory, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience extension method for serializing an object of type T, utilising the default factory.
|
||||||
|
*/
|
||||||
|
fun <T : Any> T.checkpointSerialize(serializationFactory: CheckpointSerializationFactory = CheckpointSerializationFactory.defaultFactory,
|
||||||
|
context: CheckpointSerializationContext): SerializedBytes<T> {
|
||||||
|
return serializationFactory.serialize(this, context)
|
||||||
|
}
|
@ -12,11 +12,12 @@ import net.corda.core.serialization.SerializationFactory
|
|||||||
@KeepForDJVM
|
@KeepForDJVM
|
||||||
interface SerializationEnvironment {
|
interface SerializationEnvironment {
|
||||||
val serializationFactory: SerializationFactory
|
val serializationFactory: SerializationFactory
|
||||||
|
val checkpointSerializationFactory: CheckpointSerializationFactory
|
||||||
val p2pContext: SerializationContext
|
val p2pContext: SerializationContext
|
||||||
val rpcServerContext: SerializationContext
|
val rpcServerContext: SerializationContext
|
||||||
val rpcClientContext: SerializationContext
|
val rpcClientContext: SerializationContext
|
||||||
val storageContext: SerializationContext
|
val storageContext: SerializationContext
|
||||||
val checkpointContext: SerializationContext
|
val checkpointContext: CheckpointSerializationContext
|
||||||
}
|
}
|
||||||
|
|
||||||
@KeepForDJVM
|
@KeepForDJVM
|
||||||
@ -26,18 +27,21 @@ open class SerializationEnvironmentImpl(
|
|||||||
rpcServerContext: SerializationContext? = null,
|
rpcServerContext: SerializationContext? = null,
|
||||||
rpcClientContext: SerializationContext? = null,
|
rpcClientContext: SerializationContext? = null,
|
||||||
storageContext: SerializationContext? = null,
|
storageContext: SerializationContext? = null,
|
||||||
checkpointContext: SerializationContext? = null) : SerializationEnvironment {
|
checkpointContext: CheckpointSerializationContext? = null,
|
||||||
|
checkpointSerializationFactory: CheckpointSerializationFactory? = null) : SerializationEnvironment {
|
||||||
// Those that are passed in as null are never inited:
|
// Those that are passed in as null are never inited:
|
||||||
override lateinit var rpcServerContext: SerializationContext
|
override lateinit var rpcServerContext: SerializationContext
|
||||||
override lateinit var rpcClientContext: SerializationContext
|
override lateinit var rpcClientContext: SerializationContext
|
||||||
override lateinit var storageContext: SerializationContext
|
override lateinit var storageContext: SerializationContext
|
||||||
override lateinit var checkpointContext: SerializationContext
|
override lateinit var checkpointContext: CheckpointSerializationContext
|
||||||
|
override lateinit var checkpointSerializationFactory: CheckpointSerializationFactory
|
||||||
|
|
||||||
init {
|
init {
|
||||||
rpcServerContext?.let { this.rpcServerContext = it }
|
rpcServerContext?.let { this.rpcServerContext = it }
|
||||||
rpcClientContext?.let { this.rpcClientContext = it }
|
rpcClientContext?.let { this.rpcClientContext = it }
|
||||||
storageContext?.let { this.storageContext = it }
|
storageContext?.let { this.storageContext = it }
|
||||||
checkpointContext?.let { this.checkpointContext = it }
|
checkpointContext?.let { this.checkpointContext = it }
|
||||||
|
checkpointSerializationFactory?.let { this.checkpointSerializationFactory = it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,12 +3,15 @@ package net.corda.core.transactions
|
|||||||
import net.corda.core.CordaException
|
import net.corda.core.CordaException
|
||||||
import net.corda.core.KeepForDJVM
|
import net.corda.core.KeepForDJVM
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
|
import net.corda.core.contracts.ComponentGroupEnum.*
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.serialization.*
|
import net.corda.core.serialization.*
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.util.function.Predicate
|
import java.util.function.Predicate
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implemented by [WireTransaction] and [FilteredTransaction]. A TraversableTransaction allows you to iterate
|
* Implemented by [WireTransaction] and [FilteredTransaction]. A TraversableTransaction allows you to iterate
|
||||||
@ -18,29 +21,29 @@ import java.util.function.Predicate
|
|||||||
*/
|
*/
|
||||||
abstract class TraversableTransaction(open val componentGroups: List<ComponentGroup>) : CoreTransaction() {
|
abstract class TraversableTransaction(open val componentGroups: List<ComponentGroup>) : CoreTransaction() {
|
||||||
/** Hashes of the ZIP/JAR files that are needed to interpret the contents of this wire transaction. */
|
/** Hashes of the ZIP/JAR files that are needed to interpret the contents of this wire transaction. */
|
||||||
val attachments: List<SecureHash> = deserialiseComponentGroup(ComponentGroupEnum.ATTACHMENTS_GROUP, { SerializedBytes<SecureHash>(it).deserialize() })
|
val attachments: List<SecureHash> = deserialiseComponentGroup(SecureHash::class, ATTACHMENTS_GROUP)
|
||||||
|
|
||||||
/** Pointers to the input states on the ledger, identified by (tx identity hash, output index). */
|
/** Pointers to the input states on the ledger, identified by (tx identity hash, output index). */
|
||||||
override val inputs: List<StateRef> = deserialiseComponentGroup(ComponentGroupEnum.INPUTS_GROUP, { SerializedBytes<StateRef>(it).deserialize() })
|
override val inputs: List<StateRef> = deserialiseComponentGroup(StateRef::class, INPUTS_GROUP)
|
||||||
|
|
||||||
/** Pointers to reference states, identified by (tx identity hash, output index). */
|
/** Pointers to reference states, identified by (tx identity hash, output index). */
|
||||||
override val references: List<StateRef> = deserialiseComponentGroup(ComponentGroupEnum.REFERENCES_GROUP, { SerializedBytes<StateRef>(it).deserialize() })
|
override val references: List<StateRef> = deserialiseComponentGroup(StateRef::class, REFERENCES_GROUP)
|
||||||
|
|
||||||
override val outputs: List<TransactionState<ContractState>> = deserialiseComponentGroup(ComponentGroupEnum.OUTPUTS_GROUP, { SerializedBytes<TransactionState<ContractState>>(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) })
|
override val outputs: List<TransactionState<ContractState>> = deserialiseComponentGroup(TransactionState::class, OUTPUTS_GROUP, attachmentsContext = true)
|
||||||
|
|
||||||
/** Ordered list of ([CommandData], [PublicKey]) pairs that instruct the contracts what to do. */
|
/** Ordered list of ([CommandData], [PublicKey]) pairs that instruct the contracts what to do. */
|
||||||
val commands: List<Command<*>> = deserialiseCommands()
|
val commands: List<Command<*>> = deserialiseCommands()
|
||||||
|
|
||||||
override val notary: Party? = let {
|
override val notary: Party? = let {
|
||||||
val notaries: List<Party> = deserialiseComponentGroup(ComponentGroupEnum.NOTARY_GROUP, { SerializedBytes<Party>(it).deserialize() })
|
val notaries: List<Party> = deserialiseComponentGroup(Party::class, NOTARY_GROUP)
|
||||||
check(notaries.size <= 1) { "Invalid Transaction. More than 1 notary party detected." }
|
check(notaries.size <= 1) { "Invalid Transaction. More than 1 notary party detected." }
|
||||||
if (notaries.isNotEmpty()) notaries[0] else null
|
notaries.firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
val timeWindow: TimeWindow? = let {
|
val timeWindow: TimeWindow? = let {
|
||||||
val timeWindows: List<TimeWindow> = deserialiseComponentGroup(ComponentGroupEnum.TIMEWINDOW_GROUP, { SerializedBytes<TimeWindow>(it).deserialize() })
|
val timeWindows: List<TimeWindow> = deserialiseComponentGroup(TimeWindow::class, TIMEWINDOW_GROUP)
|
||||||
check(timeWindows.size <= 1) { "Invalid Transaction. More than 1 time-window detected." }
|
check(timeWindows.size <= 1) { "Invalid Transaction. More than 1 time-window detected." }
|
||||||
if (timeWindows.isNotEmpty()) timeWindows[0] else null
|
timeWindows.firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,12 +66,16 @@ abstract class TraversableTransaction(open val componentGroups: List<ComponentGr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to return a meaningful exception if deserialisation of a component fails.
|
// Helper function to return a meaningful exception if deserialisation of a component fails.
|
||||||
private fun <T> deserialiseComponentGroup(groupEnum: ComponentGroupEnum, deserialiseBody: (ByteArray) -> T): List<T> {
|
private fun <T : Any> deserialiseComponentGroup(clazz: KClass<T>,
|
||||||
|
groupEnum: ComponentGroupEnum,
|
||||||
|
attachmentsContext: Boolean = false): List<T> {
|
||||||
|
val factory = SerializationFactory.defaultFactory
|
||||||
|
val context = factory.defaultContext.let { if (attachmentsContext) it.withAttachmentsClassLoader(attachments) else it }
|
||||||
val group = componentGroups.firstOrNull { it.groupIndex == groupEnum.ordinal }
|
val group = componentGroups.firstOrNull { it.groupIndex == groupEnum.ordinal }
|
||||||
return if (group != null && group.components.isNotEmpty()) {
|
return if (group != null && group.components.isNotEmpty()) {
|
||||||
group.components.mapIndexed { internalIndex, component ->
|
group.components.mapIndexed { internalIndex, component ->
|
||||||
try {
|
try {
|
||||||
deserialiseBody(component.bytes)
|
factory.deserialize(component, clazz.java, context)
|
||||||
} catch (e: MissingAttachmentsException) {
|
} catch (e: MissingAttachmentsException) {
|
||||||
throw e
|
throw e
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -87,11 +94,13 @@ abstract class TraversableTransaction(open val componentGroups: List<ComponentGr
|
|||||||
// TODO: we could avoid deserialising unrelated signers.
|
// TODO: we could avoid deserialising unrelated signers.
|
||||||
// However, current approach ensures the transaction is not malformed
|
// However, current approach ensures the transaction is not malformed
|
||||||
// and it will throw if any of the signers objects is not List of public keys).
|
// and it will throw if any of the signers objects is not List of public keys).
|
||||||
val signersList = deserialiseComponentGroup(ComponentGroupEnum.SIGNERS_GROUP, { SerializedBytes<List<PublicKey>>(it).deserialize() })
|
val signersList: List<List<PublicKey>> = uncheckedCast(deserialiseComponentGroup(List::class, SIGNERS_GROUP))
|
||||||
val commandDataList = deserialiseComponentGroup(ComponentGroupEnum.COMMANDS_GROUP, { SerializedBytes<CommandData>(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) })
|
val commandDataList: List<CommandData> = deserialiseComponentGroup(CommandData::class, COMMANDS_GROUP, attachmentsContext = true)
|
||||||
val group = componentGroups.firstOrNull { it.groupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal }
|
val group = componentGroups.firstOrNull { it.groupIndex == COMMANDS_GROUP.ordinal }
|
||||||
return if (group is FilteredComponentGroup) {
|
return if (group is FilteredComponentGroup) {
|
||||||
check(commandDataList.size <= signersList.size) { "Invalid Transaction. Less Signers (${signersList.size}) than CommandData (${commandDataList.size}) objects" }
|
check(commandDataList.size <= signersList.size) {
|
||||||
|
"Invalid Transaction. Less Signers (${signersList.size}) than CommandData (${commandDataList.size}) objects"
|
||||||
|
}
|
||||||
val componentHashes = group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) }
|
val componentHashes = group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) }
|
||||||
val leafIndices = componentHashes.map { group.partialMerkleTree.leafIndex(it) }
|
val leafIndices = componentHashes.map { group.partialMerkleTree.leafIndex(it) }
|
||||||
if (leafIndices.isNotEmpty())
|
if (leafIndices.isNotEmpty())
|
||||||
@ -100,7 +109,9 @@ abstract class TraversableTransaction(open val componentGroups: List<ComponentGr
|
|||||||
} else {
|
} else {
|
||||||
// It is a WireTransaction
|
// It is a WireTransaction
|
||||||
// or a FilteredTransaction with no Commands (in which case group is null).
|
// or a FilteredTransaction with no Commands (in which case group is null).
|
||||||
check(commandDataList.size == signersList.size) { "Invalid Transaction. Sizes of CommandData (${commandDataList.size}) and Signers (${signersList.size}) do not match" }
|
check(commandDataList.size == signersList.size) {
|
||||||
|
"Invalid Transaction. Sizes of CommandData (${commandDataList.size}) and Signers (${signersList.size}) do not match"
|
||||||
|
}
|
||||||
commandDataList.mapIndexed { index, commandData -> Command(commandData, signersList[index]) }
|
commandDataList.mapIndexed { index, commandData -> Command(commandData, signersList[index]) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -145,47 +156,47 @@ class FilteredTransaction internal constructor(
|
|||||||
var signersIncluded = false
|
var signersIncluded = false
|
||||||
|
|
||||||
fun <T : Any> filter(t: T, componentGroupIndex: Int, internalIndex: Int) {
|
fun <T : Any> filter(t: T, componentGroupIndex: Int, internalIndex: Int) {
|
||||||
if (filtering.test(t)) {
|
if (!filtering.test(t)) return
|
||||||
val group = filteredSerialisedComponents[componentGroupIndex]
|
|
||||||
// Because the filter passed, we know there is a match. We also use first Vs single as the init function
|
val group = filteredSerialisedComponents[componentGroupIndex]
|
||||||
// of WireTransaction ensures there are no duplicated groups.
|
// Because the filter passed, we know there is a match. We also use first Vs single as the init function
|
||||||
val serialisedComponent = wtx.componentGroups.first { it.groupIndex == componentGroupIndex }.components[internalIndex]
|
// of WireTransaction ensures there are no duplicated groups.
|
||||||
if (group == null) {
|
val serialisedComponent = wtx.componentGroups.first { it.groupIndex == componentGroupIndex }.components[internalIndex]
|
||||||
// As all of the helper Map structures, like availableComponentNonces, availableComponentHashes
|
if (group == null) {
|
||||||
// and groupsMerkleRoots, are computed lazily via componentGroups.forEach, there should always be
|
// As all of the helper Map structures, like availableComponentNonces, availableComponentHashes
|
||||||
// a match on Map.get ensuring it will never return null.
|
// and groupsMerkleRoots, are computed lazily via componentGroups.forEach, there should always be
|
||||||
filteredSerialisedComponents[componentGroupIndex] = mutableListOf(serialisedComponent)
|
// a match on Map.get ensuring it will never return null.
|
||||||
filteredComponentNonces[componentGroupIndex] = mutableListOf(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex])
|
filteredSerialisedComponents[componentGroupIndex] = mutableListOf(serialisedComponent)
|
||||||
filteredComponentHashes[componentGroupIndex] = mutableListOf(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex])
|
filteredComponentNonces[componentGroupIndex] = mutableListOf(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex])
|
||||||
} else {
|
filteredComponentHashes[componentGroupIndex] = mutableListOf(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex])
|
||||||
group.add(serialisedComponent)
|
} else {
|
||||||
// If the group[componentGroupIndex] existed, then we guarantee that
|
group.add(serialisedComponent)
|
||||||
// filteredComponentNonces[componentGroupIndex] and filteredComponentHashes[componentGroupIndex] are not null.
|
// If the group[componentGroupIndex] existed, then we guarantee that
|
||||||
filteredComponentNonces[componentGroupIndex]!!.add(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex])
|
// filteredComponentNonces[componentGroupIndex] and filteredComponentHashes[componentGroupIndex] are not null.
|
||||||
filteredComponentHashes[componentGroupIndex]!!.add(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex])
|
filteredComponentNonces[componentGroupIndex]!!.add(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex])
|
||||||
}
|
filteredComponentHashes[componentGroupIndex]!!.add(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex])
|
||||||
// If at least one command is visible, then all command-signers should be visible as well.
|
}
|
||||||
// This is required for visibility purposes, see FilteredTransaction.checkAllCommandsVisible() for more details.
|
// If at least one command is visible, then all command-signers should be visible as well.
|
||||||
if (componentGroupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal && !signersIncluded) {
|
// This is required for visibility purposes, see FilteredTransaction.checkAllCommandsVisible() for more details.
|
||||||
signersIncluded = true
|
if (componentGroupIndex == COMMANDS_GROUP.ordinal && !signersIncluded) {
|
||||||
val signersGroupIndex = ComponentGroupEnum.SIGNERS_GROUP.ordinal
|
signersIncluded = true
|
||||||
// There exist commands, thus the signers group is not empty.
|
val signersGroupIndex = SIGNERS_GROUP.ordinal
|
||||||
val signersGroupComponents = wtx.componentGroups.first { it.groupIndex == signersGroupIndex }
|
// There exist commands, thus the signers group is not empty.
|
||||||
filteredSerialisedComponents[signersGroupIndex] = signersGroupComponents.components.toMutableList()
|
val signersGroupComponents = wtx.componentGroups.first { it.groupIndex == signersGroupIndex }
|
||||||
filteredComponentNonces[signersGroupIndex] = wtx.availableComponentNonces[signersGroupIndex]!!.toMutableList()
|
filteredSerialisedComponents[signersGroupIndex] = signersGroupComponents.components.toMutableList()
|
||||||
filteredComponentHashes[signersGroupIndex] = wtx.availableComponentHashes[signersGroupIndex]!!.toMutableList()
|
filteredComponentNonces[signersGroupIndex] = wtx.availableComponentNonces[signersGroupIndex]!!.toMutableList()
|
||||||
}
|
filteredComponentHashes[signersGroupIndex] = wtx.availableComponentHashes[signersGroupIndex]!!.toMutableList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateFilteredComponents() {
|
fun updateFilteredComponents() {
|
||||||
wtx.inputs.forEachIndexed { internalIndex, it -> filter(it, ComponentGroupEnum.INPUTS_GROUP.ordinal, internalIndex) }
|
wtx.inputs.forEachIndexed { internalIndex, it -> filter(it, INPUTS_GROUP.ordinal, internalIndex) }
|
||||||
wtx.outputs.forEachIndexed { internalIndex, it -> filter(it, ComponentGroupEnum.OUTPUTS_GROUP.ordinal, internalIndex) }
|
wtx.outputs.forEachIndexed { internalIndex, it -> filter(it, OUTPUTS_GROUP.ordinal, internalIndex) }
|
||||||
wtx.commands.forEachIndexed { internalIndex, it -> filter(it, ComponentGroupEnum.COMMANDS_GROUP.ordinal, internalIndex) }
|
wtx.commands.forEachIndexed { internalIndex, it -> filter(it, COMMANDS_GROUP.ordinal, internalIndex) }
|
||||||
wtx.attachments.forEachIndexed { internalIndex, it -> filter(it, ComponentGroupEnum.ATTACHMENTS_GROUP.ordinal, internalIndex) }
|
wtx.attachments.forEachIndexed { internalIndex, it -> filter(it, ATTACHMENTS_GROUP.ordinal, internalIndex) }
|
||||||
if (wtx.notary != null) filter(wtx.notary, ComponentGroupEnum.NOTARY_GROUP.ordinal, 0)
|
if (wtx.notary != null) filter(wtx.notary, NOTARY_GROUP.ordinal, 0)
|
||||||
if (wtx.timeWindow != null) filter(wtx.timeWindow, ComponentGroupEnum.TIMEWINDOW_GROUP.ordinal, 0)
|
if (wtx.timeWindow != null) filter(wtx.timeWindow, TIMEWINDOW_GROUP.ordinal, 0)
|
||||||
wtx.references.forEachIndexed { internalIndex, it -> filter(it, ComponentGroupEnum.REFERENCES_GROUP.ordinal, internalIndex) }
|
wtx.references.forEachIndexed { internalIndex, it -> filter(it, REFERENCES_GROUP.ordinal, internalIndex) }
|
||||||
// It is highlighted that because there is no a signers property in TraversableTransaction,
|
// It is highlighted that because there is no a signers property in TraversableTransaction,
|
||||||
// one cannot specifically filter them in or out.
|
// one cannot specifically filter them in or out.
|
||||||
// The above is very important to ensure someone won't filter out the signers component group if at least one
|
// The above is very important to ensure someone won't filter out the signers component group if at least one
|
||||||
@ -195,10 +206,17 @@ class FilteredTransaction internal constructor(
|
|||||||
// we decide to filter and attach this field to a FilteredTransaction.
|
// we decide to filter and attach this field to a FilteredTransaction.
|
||||||
// An example would be to redact certain contract state types, but otherwise leave a transaction alone,
|
// An example would be to redact certain contract state types, but otherwise leave a transaction alone,
|
||||||
// including the unknown new components.
|
// including the unknown new components.
|
||||||
wtx.componentGroups.filter { it.groupIndex >= ComponentGroupEnum.values().size }.forEach { componentGroup -> componentGroup.components.forEachIndexed { internalIndex, component -> filter(component, componentGroup.groupIndex, internalIndex) } }
|
wtx.componentGroups
|
||||||
|
.filter { it.groupIndex >= values().size }
|
||||||
|
.forEach { componentGroup -> componentGroup.components.forEachIndexed { internalIndex, component -> filter(component, componentGroup.groupIndex, internalIndex) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createPartialMerkleTree(componentGroupIndex: Int) = PartialMerkleTree.build(MerkleTree.getMerkleTree(wtx.availableComponentHashes[componentGroupIndex]!!), filteredComponentHashes[componentGroupIndex]!!)
|
fun createPartialMerkleTree(componentGroupIndex: Int): PartialMerkleTree {
|
||||||
|
return PartialMerkleTree.build(
|
||||||
|
MerkleTree.getMerkleTree(wtx.availableComponentHashes[componentGroupIndex]!!),
|
||||||
|
filteredComponentHashes[componentGroupIndex]!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun createFilteredComponentGroups(): List<FilteredComponentGroup> {
|
fun createFilteredComponentGroups(): List<FilteredComponentGroup> {
|
||||||
updateFilteredComponents()
|
updateFilteredComponents()
|
||||||
@ -223,8 +241,11 @@ class FilteredTransaction internal constructor(
|
|||||||
@Throws(FilteredTransactionVerificationException::class)
|
@Throws(FilteredTransactionVerificationException::class)
|
||||||
fun verify() {
|
fun verify() {
|
||||||
verificationCheck(groupHashes.isNotEmpty()) { "At least one component group hash is required" }
|
verificationCheck(groupHashes.isNotEmpty()) { "At least one component group hash is required" }
|
||||||
// Verify the top level Merkle tree (group hashes are its leaves, including allOnesHash for empty list or null components in WireTransaction).
|
// Verify the top level Merkle tree (group hashes are its leaves, including allOnesHash for empty list or null
|
||||||
verificationCheck(MerkleTree.getMerkleTree(groupHashes).hash == id) { "Top level Merkle tree cannot be verified against transaction's id" }
|
// components in WireTransaction).
|
||||||
|
verificationCheck(MerkleTree.getMerkleTree(groupHashes).hash == id) {
|
||||||
|
"Top level Merkle tree cannot be verified against transaction's id"
|
||||||
|
}
|
||||||
|
|
||||||
// For completely blind verification (no components are included).
|
// For completely blind verification (no components are included).
|
||||||
if (filteredComponentGroups.isEmpty()) return
|
if (filteredComponentGroups.isEmpty()) return
|
||||||
@ -233,8 +254,12 @@ class FilteredTransaction internal constructor(
|
|||||||
filteredComponentGroups.forEach { (groupIndex, components, nonces, groupPartialTree) ->
|
filteredComponentGroups.forEach { (groupIndex, components, nonces, groupPartialTree) ->
|
||||||
verificationCheck(groupIndex < groupHashes.size) { "There is no matching component group hash for group $groupIndex" }
|
verificationCheck(groupIndex < groupHashes.size) { "There is no matching component group hash for group $groupIndex" }
|
||||||
val groupMerkleRoot = groupHashes[groupIndex]
|
val groupMerkleRoot = groupHashes[groupIndex]
|
||||||
verificationCheck(groupMerkleRoot == PartialMerkleTree.rootAndUsedHashes(groupPartialTree.root, mutableListOf())) { "Partial Merkle tree root and advertised full Merkle tree root for component group $groupIndex do not match" }
|
verificationCheck(groupMerkleRoot == PartialMerkleTree.rootAndUsedHashes(groupPartialTree.root, mutableListOf())) {
|
||||||
verificationCheck(groupPartialTree.verify(groupMerkleRoot, components.mapIndexed { index, component -> componentHash(nonces[index], component) })) { "Visible components in group $groupIndex cannot be verified against their partial Merkle tree" }
|
"Partial Merkle tree root and advertised full Merkle tree root for component group $groupIndex do not match"
|
||||||
|
}
|
||||||
|
verificationCheck(groupPartialTree.verify(groupMerkleRoot, components.mapIndexed { index, component -> componentHash(nonces[index], component) })) {
|
||||||
|
"Visible components in group $groupIndex cannot be verified against their partial Merkle tree"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,7 +306,9 @@ class FilteredTransaction internal constructor(
|
|||||||
val groupFullRoot = MerkleTree.getMerkleTree(group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) }).hash
|
val groupFullRoot = MerkleTree.getMerkleTree(group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) }).hash
|
||||||
visibilityCheck(groupPartialRoot == groupFullRoot) { "Some components for group ${group.groupIndex} are not visible" }
|
visibilityCheck(groupPartialRoot == groupFullRoot) { "Some components for group ${group.groupIndex} are not visible" }
|
||||||
// Verify the top level Merkle tree from groupHashes.
|
// Verify the top level Merkle tree from groupHashes.
|
||||||
visibilityCheck(MerkleTree.getMerkleTree(groupHashes).hash == id) { "Transaction is malformed. Top level Merkle tree cannot be verified against transaction's id" }
|
visibilityCheck(MerkleTree.getMerkleTree(groupHashes).hash == id) {
|
||||||
|
"Transaction is malformed. Top level Merkle tree cannot be verified against transaction's id"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,15 +323,17 @@ class FilteredTransaction internal constructor(
|
|||||||
*/
|
*/
|
||||||
@Throws(ComponentVisibilityException::class)
|
@Throws(ComponentVisibilityException::class)
|
||||||
fun checkCommandVisibility(publicKey: PublicKey) {
|
fun checkCommandVisibility(publicKey: PublicKey) {
|
||||||
val commandSigners = componentGroups.firstOrNull { it.groupIndex == ComponentGroupEnum.SIGNERS_GROUP.ordinal }
|
val commandSigners = componentGroups.firstOrNull { it.groupIndex == SIGNERS_GROUP.ordinal }
|
||||||
val expectedNumOfCommands = expectedNumOfCommands(publicKey, commandSigners)
|
val expectedNumOfCommands = expectedNumOfCommands(publicKey, commandSigners)
|
||||||
val receivedForThisKeyNumOfCommands = commands.filter { publicKey in it.signers }.size
|
val receivedForThisKeyNumOfCommands = commands.filter { publicKey in it.signers }.size
|
||||||
visibilityCheck(expectedNumOfCommands == receivedForThisKeyNumOfCommands) { "$expectedNumOfCommands commands were expected, but received $receivedForThisKeyNumOfCommands" }
|
visibilityCheck(expectedNumOfCommands == receivedForThisKeyNumOfCommands) {
|
||||||
|
"$expectedNumOfCommands commands were expected, but received $receivedForThisKeyNumOfCommands"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to return number of expected commands to sign.
|
// Function to return number of expected commands to sign.
|
||||||
private fun expectedNumOfCommands(publicKey: PublicKey, commandSigners: ComponentGroup?): Int {
|
private fun expectedNumOfCommands(publicKey: PublicKey, commandSigners: ComponentGroup?): Int {
|
||||||
checkAllComponentsVisible(ComponentGroupEnum.SIGNERS_GROUP)
|
checkAllComponentsVisible(SIGNERS_GROUP)
|
||||||
if (commandSigners == null) return 0
|
if (commandSigners == null) return 0
|
||||||
fun signersKeys (internalIndex: Int, opaqueBytes: OpaqueBytes): List<PublicKey> {
|
fun signersKeys (internalIndex: Int, opaqueBytes: OpaqueBytes): List<PublicKey> {
|
||||||
try {
|
try {
|
||||||
@ -340,7 +369,10 @@ class FilteredTransaction internal constructor(
|
|||||||
*/
|
*/
|
||||||
@KeepForDJVM
|
@KeepForDJVM
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class FilteredComponentGroup(override val groupIndex: Int, override val components: List<OpaqueBytes>, val nonces: List<SecureHash>, val partialMerkleTree: PartialMerkleTree) : ComponentGroup(groupIndex, components) {
|
data class FilteredComponentGroup(override val groupIndex: Int,
|
||||||
|
override val components: List<OpaqueBytes>,
|
||||||
|
val nonces: List<SecureHash>,
|
||||||
|
val partialMerkleTree: PartialMerkleTree) : ComponentGroup(groupIndex, components) {
|
||||||
init {
|
init {
|
||||||
check(components.size == nonces.size) { "Size of transaction components and nonces do not match" }
|
check(components.size == nonces.size) { "Size of transaction components and nonces do not match" }
|
||||||
}
|
}
|
||||||
|
@ -33,9 +33,10 @@ sealed class ByteSequence(private val _bytes: ByteArray, val offset: Int, val si
|
|||||||
fun open() = ByteArrayInputStream(_bytes, offset, size)
|
fun open() = ByteArrayInputStream(_bytes, offset, size)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a sub-sequence, that may be backed by a new byte array.
|
* Create a sub-sequence of this sequence. A copy of the underlying array may be made, if a subclass overrides
|
||||||
|
* [bytes] to do so, as [OpaqueBytes] does.
|
||||||
*
|
*
|
||||||
* @param offset The offset within this sequence to start the new sequence. Note: not the offset within the backing array.
|
* @param offset The offset within this sequence to start the new sequence. Note: not the offset within the backing array.
|
||||||
* @param size The size of the intended sub sequence.
|
* @param size The size of the intended sub sequence.
|
||||||
*/
|
*/
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
@ -43,7 +44,7 @@ sealed class ByteSequence(private val _bytes: ByteArray, val offset: Int, val si
|
|||||||
require(offset >= 0)
|
require(offset >= 0)
|
||||||
require(offset + size <= this.size)
|
require(offset + size <= this.size)
|
||||||
// Intentionally use bytes rather than _bytes, to mirror the copy-or-not behaviour of that property.
|
// Intentionally use bytes rather than _bytes, to mirror the copy-or-not behaviour of that property.
|
||||||
return if (offset == 0 && size == this.size) this else OpaqueBytesSubSequence(bytes, this.offset + offset, size)
|
return if (offset == 0 && size == this.size) this else of(bytes, this.offset + offset, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -106,7 +106,7 @@ class CordappSmokeTest {
|
|||||||
class SendBackInitiatorFlowContext(private val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
class SendBackInitiatorFlowContext(private val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
// An initiated flow calling getFlowContext on its initiator will get the context from the session-init
|
// An initiated flow calling getFlowInfo on its initiator will get the context from the session-init
|
||||||
val sessionInitContext = otherPartySession.getCounterpartyFlowInfo()
|
val sessionInitContext = otherPartySession.getCounterpartyFlowInfo()
|
||||||
otherPartySession.send(sessionInitContext)
|
otherPartySession.send(sessionInitContext)
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package net.corda.core.flows;
|
package net.corda.core.flows;
|
||||||
|
|
||||||
|
import net.corda.core.serialization.internal.CheckpointSerializationDefaults;
|
||||||
|
import net.corda.core.serialization.internal.CheckpointSerializationFactory;
|
||||||
import net.corda.core.serialization.SerializationDefaults;
|
import net.corda.core.serialization.SerializationDefaults;
|
||||||
import net.corda.core.serialization.SerializationFactory;
|
import net.corda.core.serialization.SerializationFactory;
|
||||||
import net.corda.testing.core.SerializationEnvironmentRule;
|
import net.corda.testing.core.SerializationEnvironmentRule;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static net.corda.core.serialization.internal.CheckpointSerializationAPIKt.checkpointSerialize;
|
||||||
import static net.corda.core.serialization.SerializationAPIKt.serialize;
|
import static net.corda.core.serialization.SerializationAPIKt.serialize;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
@ -28,10 +31,13 @@ public class SerializationApiInJavaTest {
|
|||||||
public void enforceSerializationDefaultsApi() {
|
public void enforceSerializationDefaultsApi() {
|
||||||
SerializationDefaults defaults = SerializationDefaults.INSTANCE;
|
SerializationDefaults defaults = SerializationDefaults.INSTANCE;
|
||||||
SerializationFactory factory = defaults.getSERIALIZATION_FACTORY();
|
SerializationFactory factory = defaults.getSERIALIZATION_FACTORY();
|
||||||
|
|
||||||
|
CheckpointSerializationDefaults checkpointDefaults = CheckpointSerializationDefaults.INSTANCE;
|
||||||
|
CheckpointSerializationFactory checkpointSerializationFactory = checkpointDefaults.getCHECKPOINT_SERIALIZATION_FACTORY();
|
||||||
serialize("hello", factory, defaults.getP2P_CONTEXT());
|
serialize("hello", factory, defaults.getP2P_CONTEXT());
|
||||||
serialize("hello", factory, defaults.getRPC_SERVER_CONTEXT());
|
serialize("hello", factory, defaults.getRPC_SERVER_CONTEXT());
|
||||||
serialize("hello", factory, defaults.getRPC_CLIENT_CONTEXT());
|
serialize("hello", factory, defaults.getRPC_CLIENT_CONTEXT());
|
||||||
serialize("hello", factory, defaults.getSTORAGE_CONTEXT());
|
serialize("hello", factory, defaults.getSTORAGE_CONTEXT());
|
||||||
serialize("hello", factory, defaults.getCHECKPOINT_CONTEXT());
|
checkpointSerialize("hello", checkpointSerializationFactory, checkpointDefaults.getCHECKPOINT_CONTEXT());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,7 @@ internal class UseRefState(val linearId: UniqueIdentifier) : FlowLogic<SignedTra
|
|||||||
val notary = serviceHub.networkMapCache.notaryIdentities.first()
|
val notary = serviceHub.networkMapCache.notaryIdentities.first()
|
||||||
val query = QueryCriteria.LinearStateQueryCriteria(
|
val query = QueryCriteria.LinearStateQueryCriteria(
|
||||||
linearId = listOf(linearId),
|
linearId = listOf(linearId),
|
||||||
isRelevant = Vault.RelevancyStatus.ALL
|
relevancyStatus = Vault.RelevancyStatus.ALL
|
||||||
)
|
)
|
||||||
val referenceState = serviceHub.vaultService.queryBy<ContractState>(query).states.single()
|
val referenceState = serviceHub.vaultService.queryBy<ContractState>(query).states.single()
|
||||||
return subFlow(FinalityFlow(
|
return subFlow(FinalityFlow(
|
||||||
|
@ -8,7 +8,7 @@ import kotlin.test.assertFailsWith
|
|||||||
class CertRoleTests {
|
class CertRoleTests {
|
||||||
@Test
|
@Test
|
||||||
fun `should deserialize valid value`() {
|
fun `should deserialize valid value`() {
|
||||||
val expected = CertRole.INTERMEDIATE_CA
|
val expected = CertRole.DOORMAN_CA
|
||||||
val actual = CertRole.getInstance(ASN1Integer(1L))
|
val actual = CertRole.getInstance(ASN1Integer(1L))
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
package net.corda.core.internal.cordapp
|
||||||
|
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class CordappInfoResolverTest {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
@After
|
||||||
|
fun clearCordappInfoResolver() {
|
||||||
|
CordappInfoResolver.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test()
|
||||||
|
fun `The correct cordapp resolver is used after calling withCordappResolution`() {
|
||||||
|
val defaultTargetVersion = 222
|
||||||
|
|
||||||
|
CordappInfoResolver.register(listOf(javaClass.name), CordappImpl.Info("test", "test", "2", 3, defaultTargetVersion))
|
||||||
|
assertEquals(defaultTargetVersion, returnCallingTargetVersion())
|
||||||
|
|
||||||
|
val expectedTargetVersion = 555
|
||||||
|
CordappInfoResolver.withCordappInfoResolution( { CordappImpl.Info("foo", "bar", "1", 2, expectedTargetVersion) })
|
||||||
|
{
|
||||||
|
val actualTargetVersion = returnCallingTargetVersion()
|
||||||
|
assertEquals(expectedTargetVersion, actualTargetVersion)
|
||||||
|
}
|
||||||
|
assertEquals(defaultTargetVersion, returnCallingTargetVersion())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test()
|
||||||
|
fun `When more than one cordapp is registered for the same class, the resolver returns null`() {
|
||||||
|
CordappInfoResolver.register(listOf(javaClass.name), CordappImpl.Info("test", "test", "2", 3, 222))
|
||||||
|
CordappInfoResolver.register(listOf(javaClass.name), CordappImpl.Info("test1", "test1", "1", 2, 456))
|
||||||
|
assertEquals(0, returnCallingTargetVersion())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun returnCallingTargetVersion(): Int {
|
||||||
|
return CordappInfoResolver.getCorDappInfo()?.targetPlatformVersion ?: 0
|
||||||
|
}
|
||||||
|
}
|
@ -3,9 +3,10 @@ package net.corda.core.utilities
|
|||||||
import com.esotericsoftware.kryo.KryoException
|
import com.esotericsoftware.kryo.KryoException
|
||||||
import net.corda.core.crypto.random63BitValue
|
import net.corda.core.crypto.random63BitValue
|
||||||
import net.corda.core.serialization.*
|
import net.corda.core.serialization.*
|
||||||
|
import net.corda.core.serialization.internal.checkpointDeserialize
|
||||||
|
import net.corda.core.serialization.internal.checkpointSerialize
|
||||||
import net.corda.node.serialization.kryo.KRYO_CHECKPOINT_CONTEXT
|
import net.corda.node.serialization.kryo.KRYO_CHECKPOINT_CONTEXT
|
||||||
import net.corda.node.serialization.kryo.kryoMagic
|
import net.corda.serialization.internal.CheckpointSerializationContextImpl
|
||||||
import net.corda.serialization.internal.SerializationContextImpl
|
|
||||||
import net.corda.testing.core.SerializationEnvironmentRule
|
import net.corda.testing.core.SerializationEnvironmentRule
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
@ -24,12 +25,11 @@ class KotlinUtilsTest {
|
|||||||
@Rule
|
@Rule
|
||||||
val expectedEx: ExpectedException = ExpectedException.none()
|
val expectedEx: ExpectedException = ExpectedException.none()
|
||||||
|
|
||||||
private val KRYO_CHECKPOINT_NOWHITELIST_CONTEXT = SerializationContextImpl(kryoMagic,
|
private val KRYO_CHECKPOINT_NOWHITELIST_CONTEXT = CheckpointSerializationContextImpl(
|
||||||
javaClass.classLoader,
|
javaClass.classLoader,
|
||||||
EmptyWhitelist,
|
EmptyWhitelist,
|
||||||
emptyMap(),
|
emptyMap(),
|
||||||
true,
|
true,
|
||||||
SerializationContext.UseCase.Checkpoint,
|
|
||||||
null)
|
null)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -44,7 +44,7 @@ class KotlinUtilsTest {
|
|||||||
fun `checkpointing a transient property with non-capturing lambda`() {
|
fun `checkpointing a transient property with non-capturing lambda`() {
|
||||||
val original = NonCapturingTransientProperty()
|
val original = NonCapturingTransientProperty()
|
||||||
val originalVal = original.transientVal
|
val originalVal = original.transientVal
|
||||||
val copy = original.serialize(context = KRYO_CHECKPOINT_CONTEXT).deserialize(context = KRYO_CHECKPOINT_CONTEXT)
|
val copy = original.checkpointSerialize(context = KRYO_CHECKPOINT_CONTEXT).checkpointDeserialize(context = KRYO_CHECKPOINT_CONTEXT)
|
||||||
val copyVal = copy.transientVal
|
val copyVal = copy.transientVal
|
||||||
assertThat(copyVal).isNotEqualTo(originalVal)
|
assertThat(copyVal).isNotEqualTo(originalVal)
|
||||||
assertThat(copy.transientVal).isEqualTo(copyVal)
|
assertThat(copy.transientVal).isEqualTo(copyVal)
|
||||||
@ -55,14 +55,14 @@ class KotlinUtilsTest {
|
|||||||
expectedEx.expect(KryoException::class.java)
|
expectedEx.expect(KryoException::class.java)
|
||||||
expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization")
|
expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization")
|
||||||
val original = NonCapturingTransientProperty()
|
val original = NonCapturingTransientProperty()
|
||||||
original.serialize(context = KRYO_CHECKPOINT_CONTEXT).deserialize(context = KRYO_CHECKPOINT_NOWHITELIST_CONTEXT)
|
original.checkpointSerialize(context = KRYO_CHECKPOINT_CONTEXT).checkpointDeserialize(context = KRYO_CHECKPOINT_NOWHITELIST_CONTEXT)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `checkpointing a transient property with capturing lambda`() {
|
fun `checkpointing a transient property with capturing lambda`() {
|
||||||
val original = CapturingTransientProperty("Hello")
|
val original = CapturingTransientProperty("Hello")
|
||||||
val originalVal = original.transientVal
|
val originalVal = original.transientVal
|
||||||
val copy = original.serialize(context = KRYO_CHECKPOINT_CONTEXT).deserialize(context = KRYO_CHECKPOINT_CONTEXT)
|
val copy = original.checkpointSerialize(context = KRYO_CHECKPOINT_CONTEXT).checkpointDeserialize(context = KRYO_CHECKPOINT_CONTEXT)
|
||||||
val copyVal = copy.transientVal
|
val copyVal = copy.transientVal
|
||||||
assertThat(copyVal).isNotEqualTo(originalVal)
|
assertThat(copyVal).isNotEqualTo(originalVal)
|
||||||
assertThat(copy.transientVal).isEqualTo(copyVal)
|
assertThat(copy.transientVal).isEqualTo(copyVal)
|
||||||
@ -76,7 +76,7 @@ class KotlinUtilsTest {
|
|||||||
|
|
||||||
val original = CapturingTransientProperty("Hello")
|
val original = CapturingTransientProperty("Hello")
|
||||||
|
|
||||||
original.serialize(context = KRYO_CHECKPOINT_CONTEXT).deserialize(context = KRYO_CHECKPOINT_NOWHITELIST_CONTEXT)
|
original.checkpointSerialize(context = KRYO_CHECKPOINT_CONTEXT).checkpointDeserialize(context = KRYO_CHECKPOINT_NOWHITELIST_CONTEXT)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class NullTransientProperty {
|
private class NullTransientProperty {
|
||||||
|
@ -1,18 +1,35 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.github.johnrengelman.shadow' version '2.0.4'
|
id 'com.github.johnrengelman.shadow'
|
||||||
}
|
}
|
||||||
|
apply plugin: 'net.corda.plugins.publish-utils'
|
||||||
|
apply plugin: 'com.jfrog.artifactory'
|
||||||
|
apply plugin: 'idea'
|
||||||
|
|
||||||
|
description 'Corda deterministic JVM sandbox'
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
// Shaded version of ASM to avoid conflict with root project.
|
// Shaded version of ASM to avoid conflict with root project.
|
||||||
asm_version = '6.1.1'
|
asm_version = '6.2.1'
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url "$artifactory_contextUrl/corda-dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
testCompile.extendsFrom shadow
|
||||||
|
jdkRt.resolutionStrategy {
|
||||||
|
// Always check the repository for a newer SNAPSHOT.
|
||||||
|
cacheChangingModulesFor 0, 'seconds'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
shadow "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
shadow "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
compile "org.slf4j:jul-to-slf4j:$slf4j_version"
|
shadow "org.slf4j:slf4j-api:$slf4j_version"
|
||||||
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
|
||||||
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
|
|
||||||
|
|
||||||
// ASM: byte code manipulation library
|
// ASM: byte code manipulation library
|
||||||
compile "org.ow2.asm:asm:$asm_version"
|
compile "org.ow2.asm:asm:$asm_version"
|
||||||
@ -20,30 +37,40 @@ dependencies {
|
|||||||
compile "org.ow2.asm:asm-commons:$asm_version"
|
compile "org.ow2.asm:asm-commons:$asm_version"
|
||||||
|
|
||||||
// Classpath scanner
|
// Classpath scanner
|
||||||
compile "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version"
|
shadow "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version"
|
||||||
|
|
||||||
// Test utilities
|
// Test utilities
|
||||||
testCompile "junit:junit:$junit_version"
|
testCompile "junit:junit:$junit_version"
|
||||||
testCompile "org.assertj:assertj-core:$assertj_version"
|
testCompile "org.assertj:assertj-core:$assertj_version"
|
||||||
|
testCompile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
||||||
|
jdkRt "net.corda:deterministic-rt:latest.integration"
|
||||||
}
|
}
|
||||||
|
|
||||||
jar.enabled = false
|
jar.enabled = false
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
baseName = "djvm"
|
baseName 'corda-djvm'
|
||||||
classifier = ""
|
classifier ''
|
||||||
dependencies {
|
|
||||||
exclude(dependency('com.jcabi:.*:.*'))
|
|
||||||
exclude(dependency('org.apache.*:.*:.*'))
|
|
||||||
exclude(dependency('org.jetbrains.*:.*:.*'))
|
|
||||||
exclude(dependency('org.slf4j:.*:.*'))
|
|
||||||
exclude(dependency('io.github.lukehutch:.*:.*'))
|
|
||||||
}
|
|
||||||
relocate 'org.objectweb.asm', 'djvm.org.objectweb.asm'
|
relocate 'org.objectweb.asm', 'djvm.org.objectweb.asm'
|
||||||
artifacts {
|
|
||||||
shadow(tasks.shadowJar.archivePath) {
|
|
||||||
builtBy shadowJar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
assemble.dependsOn shadowJar
|
assemble.dependsOn shadowJar
|
||||||
|
|
||||||
|
tasks.withType(Test) {
|
||||||
|
systemProperty 'deterministic-rt.path', configurations.jdkRt.asPath
|
||||||
|
}
|
||||||
|
|
||||||
|
artifacts {
|
||||||
|
publish shadowJar
|
||||||
|
}
|
||||||
|
|
||||||
|
publish {
|
||||||
|
dependenciesFrom configurations.shadow
|
||||||
|
name shadowJar.baseName
|
||||||
|
}
|
||||||
|
|
||||||
|
idea {
|
||||||
|
module {
|
||||||
|
downloadJavadoc = true
|
||||||
|
downloadSources = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -15,12 +15,10 @@ configurations {
|
|||||||
dependencies {
|
dependencies {
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
compile "org.slf4j:jul-to-slf4j:$slf4j_version"
|
|
||||||
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
||||||
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
|
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
|
||||||
|
|
||||||
compile "info.picocli:picocli:$picocli_version"
|
compile "info.picocli:picocli:$picocli_version"
|
||||||
compile "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version"
|
|
||||||
compile project(path: ":djvm", configuration: "shadow")
|
compile project(path: ":djvm", configuration: "shadow")
|
||||||
|
|
||||||
// Deterministic runtime - used in whitelist generation
|
// Deterministic runtime - used in whitelist generation
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
package net.corda.djvm.tools.cli
|
package net.corda.djvm.tools.cli
|
||||||
|
|
||||||
import net.corda.djvm.tools.Utilities.createCodePath
|
|
||||||
import net.corda.djvm.tools.Utilities.getFileNames
|
|
||||||
import net.corda.djvm.tools.Utilities.jarPath
|
|
||||||
import picocli.CommandLine.Command
|
import picocli.CommandLine.Command
|
||||||
import picocli.CommandLine.Parameters
|
import picocli.CommandLine.Parameters
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
@ -7,9 +7,6 @@ import net.corda.djvm.execution.*
|
|||||||
import net.corda.djvm.references.ClassModule
|
import net.corda.djvm.references.ClassModule
|
||||||
import net.corda.djvm.source.ClassSource
|
import net.corda.djvm.source.ClassSource
|
||||||
import net.corda.djvm.source.SourceClassLoader
|
import net.corda.djvm.source.SourceClassLoader
|
||||||
import net.corda.djvm.tools.Utilities.find
|
|
||||||
import net.corda.djvm.tools.Utilities.onEmpty
|
|
||||||
import net.corda.djvm.tools.Utilities.userClassPath
|
|
||||||
import net.corda.djvm.utilities.Discovery
|
import net.corda.djvm.utilities.Discovery
|
||||||
import djvm.org.objectweb.asm.ClassReader
|
import djvm.org.objectweb.asm.ClassReader
|
||||||
import picocli.CommandLine.Option
|
import picocli.CommandLine.Option
|
||||||
@ -66,7 +63,7 @@ abstract class ClassCommand : CommandBase() {
|
|||||||
|
|
||||||
private lateinit var classLoader: ClassLoader
|
private lateinit var classLoader: ClassLoader
|
||||||
|
|
||||||
protected var executor = SandboxExecutor<Any, Any>()
|
protected var executor = SandboxExecutor<Any, Any>(SandboxConfiguration.DEFAULT)
|
||||||
|
|
||||||
private var derivedWhitelist: Whitelist = Whitelist.MINIMAL
|
private var derivedWhitelist: Whitelist = Whitelist.MINIMAL
|
||||||
|
|
||||||
@ -117,7 +114,7 @@ abstract class ClassCommand : CommandBase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun findDiscoverableRunnables(filters: Array<String>): List<Class<*>> {
|
private fun findDiscoverableRunnables(filters: Array<String>): List<Class<*>> {
|
||||||
val classes = find<DiscoverableRunnable>()
|
val classes = find<java.util.function.Function<*,*>>()
|
||||||
val applicableFilters = filters
|
val applicableFilters = filters
|
||||||
.filter { !isJarFile(it) && !isFullClassName(it) }
|
.filter { !isJarFile(it) && !isFullClassName(it) }
|
||||||
val filteredClasses = applicableFilters
|
val filteredClasses = applicableFilters
|
||||||
@ -128,7 +125,7 @@ abstract class ClassCommand : CommandBase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (applicableFilters.isNotEmpty() && filteredClasses.isEmpty()) {
|
if (applicableFilters.isNotEmpty() && filteredClasses.isEmpty()) {
|
||||||
throw Exception("Could not find any classes implementing ${SandboxedRunnable::class.java.simpleName} " +
|
throw Exception("Could not find any classes implementing ${java.util.function.Function::class.java.simpleName} " +
|
||||||
"whose name matches '${applicableFilters.joinToString(" ")}'")
|
"whose name matches '${applicableFilters.joinToString(" ")}'")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +189,7 @@ abstract class ClassCommand : CommandBase() {
|
|||||||
profile = profile,
|
profile = profile,
|
||||||
rules = if (ignoreRules) { emptyList() } else { Discovery.find() },
|
rules = if (ignoreRules) { emptyList() } else { Discovery.find() },
|
||||||
emitters = ignoreEmitters.emptyListIfTrueOtherwiseNull(),
|
emitters = ignoreEmitters.emptyListIfTrueOtherwiseNull(),
|
||||||
definitionProviders = if(ignoreDefinitionProviders) { emptyList() } else { Discovery.find() },
|
definitionProviders = if (ignoreDefinitionProviders) { emptyList() } else { Discovery.find() },
|
||||||
enableTracing = !disableTracing,
|
enableTracing = !disableTracing,
|
||||||
analysisConfiguration = AnalysisConfiguration(
|
analysisConfiguration = AnalysisConfiguration(
|
||||||
whitelist = whitelist,
|
whitelist = whitelist,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package net.corda.djvm.tools.cli
|
package net.corda.djvm.tools.cli
|
||||||
|
|
||||||
import net.corda.djvm.source.ClassSource
|
import net.corda.djvm.source.ClassSource
|
||||||
import net.corda.djvm.tools.Utilities.createCodePath
|
|
||||||
import picocli.CommandLine.Command
|
import picocli.CommandLine.Command
|
||||||
import picocli.CommandLine.Parameters
|
import picocli.CommandLine.Parameters
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
package net.corda.djvm.tools.cli
|
package net.corda.djvm.tools.cli
|
||||||
|
|
||||||
import net.corda.djvm.tools.Utilities.baseName
|
|
||||||
import net.corda.djvm.tools.Utilities.createCodePath
|
|
||||||
import net.corda.djvm.tools.Utilities.getFiles
|
|
||||||
import net.corda.djvm.tools.Utilities.openOptions
|
|
||||||
import picocli.CommandLine.*
|
import picocli.CommandLine.*
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.corda.djvm.tools.cli
|
package net.corda.djvm.tools.cli
|
||||||
|
|
||||||
import net.corda.djvm.execution.SandboxedRunnable
|
|
||||||
import net.corda.djvm.source.ClassSource
|
import net.corda.djvm.source.ClassSource
|
||||||
import picocli.CommandLine.Command
|
import picocli.CommandLine.Command
|
||||||
import picocli.CommandLine.Parameters
|
import picocli.CommandLine.Parameters
|
||||||
@ -20,7 +19,7 @@ class RunCommand : ClassCommand() {
|
|||||||
var classes: Array<String> = emptyArray()
|
var classes: Array<String> = emptyArray()
|
||||||
|
|
||||||
override fun processClasses(classes: List<Class<*>>) {
|
override fun processClasses(classes: List<Class<*>>) {
|
||||||
val interfaceName = SandboxedRunnable::class.java.simpleName
|
val interfaceName = java.util.function.Function::class.java.simpleName
|
||||||
for (clazz in classes) {
|
for (clazz in classes) {
|
||||||
if (!clazz.interfaces.any { it.simpleName == interfaceName }) {
|
if (!clazz.interfaces.any { it.simpleName == interfaceName }) {
|
||||||
printError("Class is not an instance of $interfaceName; ${clazz.name}")
|
printError("Class is not an instance of $interfaceName; ${clazz.name}")
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.corda.djvm.tools.cli
|
package net.corda.djvm.tools.cli
|
||||||
|
|
||||||
import net.corda.djvm.tools.Utilities.workingDirectory
|
|
||||||
import picocli.CommandLine.Command
|
import picocli.CommandLine.Command
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
|
|
||||||
|
104
djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Utilities.kt
Normal file
104
djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Utilities.kt
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
@file:JvmName("Utilities")
|
||||||
|
package net.corda.djvm.tools.cli
|
||||||
|
|
||||||
|
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
|
||||||
|
import java.lang.reflect.Modifier
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.nio.file.StandardOpenOption
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the expanded file name of each path in the provided array.
|
||||||
|
*/
|
||||||
|
fun Array<Path>?.getFiles(map: (Path) -> Path = { it }) = (this ?: emptyArray()).map {
|
||||||
|
val pathString = it.toString()
|
||||||
|
val path = map(it)
|
||||||
|
when {
|
||||||
|
'/' in pathString || '\\' in pathString ->
|
||||||
|
throw Exception("Please provide a pathless file name")
|
||||||
|
pathString.endsWith(".java", true) -> path
|
||||||
|
else -> Paths.get("$path.java")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the string representation of each expanded file name in the provided array.
|
||||||
|
*/
|
||||||
|
fun Array<Path>?.getFileNames(map: (Path) -> Path = { it }) = this.getFiles(map).map {
|
||||||
|
it.toString()
|
||||||
|
}.toTypedArray()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute inlined action if the collection is empty.
|
||||||
|
*/
|
||||||
|
inline fun <T> List<T>.onEmpty(action: () -> Unit): List<T> {
|
||||||
|
if (!this.any()) {
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute inlined action if the array is empty.
|
||||||
|
*/
|
||||||
|
inline fun <reified T> Array<T>?.onEmpty(action: () -> Unit): Array<T> {
|
||||||
|
return (this ?: emptyArray()).toList().onEmpty(action).toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derive the set of [StandardOpenOption]'s to use for a file operation.
|
||||||
|
*/
|
||||||
|
fun openOptions(force: Boolean) = if (force) {
|
||||||
|
arrayOf(StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)
|
||||||
|
} else {
|
||||||
|
arrayOf(StandardOpenOption.CREATE_NEW)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the path of where any generated code will be placed. Create the directory if it does not exist.
|
||||||
|
*/
|
||||||
|
fun createCodePath(): Path {
|
||||||
|
return Paths.get("tmp", "net", "corda", "djvm").let {
|
||||||
|
Files.createDirectories(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the base name of a file (i.e., its name without extension)
|
||||||
|
*/
|
||||||
|
val Path.baseName: String
|
||||||
|
get() = this.fileName.toString()
|
||||||
|
.replaceAfterLast('.', "")
|
||||||
|
.removeSuffix(".")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path of the executing JAR.
|
||||||
|
*/
|
||||||
|
val jarPath: String = object {}.javaClass.protectionDomain.codeSource.location.toURI().path
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path of the current working directory.
|
||||||
|
*/
|
||||||
|
val workingDirectory: Path = Paths.get(System.getProperty("user.dir"))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class path for the current execution context.
|
||||||
|
*/
|
||||||
|
val userClassPath: String = System.getProperty("java.class.path")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a reference of each concrete class that implements interface or class [T].
|
||||||
|
*/
|
||||||
|
inline fun <reified T> find(scanSpec: String = "net/corda/djvm"): List<Class<*>> {
|
||||||
|
val references = mutableListOf<Class<*>>()
|
||||||
|
FastClasspathScanner(scanSpec)
|
||||||
|
.matchClassesImplementing(T::class.java) { clazz ->
|
||||||
|
if (!Modifier.isAbstract(clazz.modifiers) && !Modifier.isStatic(clazz.modifiers)) {
|
||||||
|
references.add(clazz)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.scan()
|
||||||
|
return references
|
||||||
|
}
|
@ -33,52 +33,53 @@ class WhitelistGenerateCommand : CommandBase() {
|
|||||||
override fun validateArguments() = paths.isNotEmpty()
|
override fun validateArguments() = paths.isNotEmpty()
|
||||||
|
|
||||||
override fun handleCommand(): Boolean {
|
override fun handleCommand(): Boolean {
|
||||||
val entries = mutableListOf<String>()
|
val entries = AnalysisConfiguration().use { configuration ->
|
||||||
val visitor = object : ClassAndMemberVisitor() {
|
val entries = mutableListOf<String>()
|
||||||
override fun visitClass(clazz: ClassRepresentation): ClassRepresentation {
|
val visitor = object : ClassAndMemberVisitor(configuration, null) {
|
||||||
entries.add(clazz.name)
|
override fun visitClass(clazz: ClassRepresentation): ClassRepresentation {
|
||||||
return super.visitClass(clazz)
|
entries.add(clazz.name)
|
||||||
}
|
return super.visitClass(clazz)
|
||||||
|
}
|
||||||
|
|
||||||
override fun visitMethod(clazz: ClassRepresentation, method: Member): Member {
|
override fun visitMethod(clazz: ClassRepresentation, method: Member): Member {
|
||||||
visitMember(clazz, method)
|
visitMember(clazz, method)
|
||||||
return super.visitMethod(clazz, method)
|
return super.visitMethod(clazz, method)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitField(clazz: ClassRepresentation, field: Member): Member {
|
override fun visitField(clazz: ClassRepresentation, field: Member): Member {
|
||||||
visitMember(clazz, field)
|
visitMember(clazz, field)
|
||||||
return super.visitField(clazz, field)
|
return super.visitField(clazz, field)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun visitMember(clazz: ClassRepresentation, member: Member) {
|
private fun visitMember(clazz: ClassRepresentation, member: Member) {
|
||||||
entries.add("${clazz.name}.${member.memberName}:${member.signature}")
|
entries.add("${clazz.name}.${member.memberName}:${member.signature}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
val context = AnalysisContext.fromConfiguration(configuration)
|
||||||
|
for (path in paths) {
|
||||||
|
ClassSource.fromPath(path).getStreamIterator().forEach {
|
||||||
|
visitor.analyze(it, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entries
|
||||||
}
|
}
|
||||||
val context = AnalysisContext.fromConfiguration(AnalysisConfiguration(), emptyList())
|
output?.also {
|
||||||
for (path in paths) {
|
Files.newOutputStream(it, StandardOpenOption.CREATE).use { out ->
|
||||||
ClassSource.fromPath(path).getStreamIterator().forEach {
|
GZIPOutputStream(out).use { gzip ->
|
||||||
visitor.analyze(it, context)
|
PrintStream(gzip).use { pout ->
|
||||||
}
|
pout.println("""
|
||||||
}
|
|
||||||
val output = output
|
|
||||||
if (output != null) {
|
|
||||||
Files.newOutputStream(output, StandardOpenOption.CREATE).use {
|
|
||||||
GZIPOutputStream(it).use {
|
|
||||||
PrintStream(it).use {
|
|
||||||
it.println("""
|
|
||||||
|java/.*
|
|java/.*
|
||||||
|javax/.*
|
|javax/.*
|
||||||
|jdk/.*
|
|jdk/.*
|
||||||
|
|com/sun/.*
|
||||||
|sun/.*
|
|sun/.*
|
||||||
|---
|
|---
|
||||||
""".trimMargin().trim())
|
""".trimMargin().trim())
|
||||||
printEntries(it, entries)
|
printEntries(pout, entries)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} ?: printEntries(System.out, entries)
|
||||||
printEntries(System.out, entries)
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,25 +3,20 @@ package net.corda.djvm
|
|||||||
import net.corda.djvm.analysis.AnalysisContext
|
import net.corda.djvm.analysis.AnalysisContext
|
||||||
import net.corda.djvm.costing.RuntimeCostSummary
|
import net.corda.djvm.costing.RuntimeCostSummary
|
||||||
import net.corda.djvm.rewiring.SandboxClassLoader
|
import net.corda.djvm.rewiring.SandboxClassLoader
|
||||||
import net.corda.djvm.source.ClassSource
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The context in which a sandboxed operation is run.
|
* The context in which a sandboxed operation is run.
|
||||||
*
|
*
|
||||||
* @property configuration The configuration of the sandbox.
|
* @property configuration The configuration of the sandbox.
|
||||||
* @property inputClasses The classes passed in for analysis.
|
|
||||||
*/
|
*/
|
||||||
class SandboxRuntimeContext(
|
class SandboxRuntimeContext(val configuration: SandboxConfiguration) {
|
||||||
val configuration: SandboxConfiguration,
|
|
||||||
private val inputClasses: List<ClassSource>
|
|
||||||
) {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The class loader to use inside the sandbox.
|
* The class loader to use inside the sandbox.
|
||||||
*/
|
*/
|
||||||
val classLoader: SandboxClassLoader = SandboxClassLoader(
|
val classLoader: SandboxClassLoader = SandboxClassLoader(
|
||||||
configuration,
|
configuration,
|
||||||
AnalysisContext.fromConfiguration(configuration.analysisConfiguration, inputClasses)
|
AnalysisContext.fromConfiguration(configuration.analysisConfiguration)
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,7 +30,7 @@ class SandboxRuntimeContext(
|
|||||||
fun use(action: SandboxRuntimeContext.() -> Unit) {
|
fun use(action: SandboxRuntimeContext.() -> Unit) {
|
||||||
SandboxRuntimeContext.instance = this
|
SandboxRuntimeContext.instance = this
|
||||||
try {
|
try {
|
||||||
this.action()
|
action(this)
|
||||||
} finally {
|
} finally {
|
||||||
threadLocalContext.remove()
|
threadLocalContext.remove()
|
||||||
}
|
}
|
||||||
@ -43,9 +38,7 @@ class SandboxRuntimeContext(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private val threadLocalContext = object : ThreadLocal<SandboxRuntimeContext?>() {
|
private val threadLocalContext = ThreadLocal<SandboxRuntimeContext?>()
|
||||||
override fun initialValue(): SandboxRuntimeContext? = null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When called from within a sandbox, this returns the context for the current sandbox thread.
|
* When called from within a sandbox, this returns the context for the current sandbox thread.
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
package net.corda.djvm.analysis
|
package net.corda.djvm.analysis
|
||||||
|
|
||||||
|
import net.corda.djvm.code.ruleViolationError
|
||||||
|
import net.corda.djvm.code.thresholdViolationError
|
||||||
import net.corda.djvm.messages.Severity
|
import net.corda.djvm.messages.Severity
|
||||||
import net.corda.djvm.references.ClassModule
|
import net.corda.djvm.references.ClassModule
|
||||||
import net.corda.djvm.references.MemberModule
|
import net.corda.djvm.references.MemberModule
|
||||||
|
import net.corda.djvm.source.BootstrapClassLoader
|
||||||
|
import net.corda.djvm.source.SourceClassLoader
|
||||||
import sandbox.net.corda.djvm.costing.RuntimeCostAccounter
|
import sandbox.net.corda.djvm.costing.RuntimeCostAccounter
|
||||||
|
import java.io.Closeable
|
||||||
|
import java.io.IOException
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -13,7 +19,8 @@ import java.nio.file.Path
|
|||||||
* @param additionalPinnedClasses Classes that have already been declared in the sandbox namespace and that should be
|
* @param additionalPinnedClasses Classes that have already been declared in the sandbox namespace and that should be
|
||||||
* made available inside the sandboxed environment.
|
* made available inside the sandboxed environment.
|
||||||
* @property minimumSeverityLevel The minimum severity level to log and report.
|
* @property minimumSeverityLevel The minimum severity level to log and report.
|
||||||
* @property classPath The extended class path to use for the analysis.
|
* @param classPath The extended class path to use for the analysis.
|
||||||
|
* @param bootstrapJar The location of a jar containing the Java APIs.
|
||||||
* @property analyzeAnnotations Analyze annotations despite not being explicitly referenced.
|
* @property analyzeAnnotations Analyze annotations despite not being explicitly referenced.
|
||||||
* @property prefixFilters Only record messages where the originating class name matches one of the provided prefixes.
|
* @property prefixFilters Only record messages where the originating class name matches one of the provided prefixes.
|
||||||
* If none are provided, all messages will be reported.
|
* If none are provided, all messages will be reported.
|
||||||
@ -24,32 +31,47 @@ class AnalysisConfiguration(
|
|||||||
val whitelist: Whitelist = Whitelist.MINIMAL,
|
val whitelist: Whitelist = Whitelist.MINIMAL,
|
||||||
additionalPinnedClasses: Set<String> = emptySet(),
|
additionalPinnedClasses: Set<String> = emptySet(),
|
||||||
val minimumSeverityLevel: Severity = Severity.WARNING,
|
val minimumSeverityLevel: Severity = Severity.WARNING,
|
||||||
val classPath: List<Path> = emptyList(),
|
classPath: List<Path> = emptyList(),
|
||||||
|
bootstrapJar: Path? = null,
|
||||||
val analyzeAnnotations: Boolean = false,
|
val analyzeAnnotations: Boolean = false,
|
||||||
val prefixFilters: List<String> = emptyList(),
|
val prefixFilters: List<String> = emptyList(),
|
||||||
val classModule: ClassModule = ClassModule(),
|
val classModule: ClassModule = ClassModule(),
|
||||||
val memberModule: MemberModule = MemberModule()
|
val memberModule: MemberModule = MemberModule()
|
||||||
) {
|
) : Closeable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Classes that have already been declared in the sandbox namespace and that should be made
|
* Classes that have already been declared in the sandbox namespace and that should be made
|
||||||
* available inside the sandboxed environment.
|
* available inside the sandboxed environment.
|
||||||
*/
|
*/
|
||||||
val pinnedClasses: Set<String> = setOf(SANDBOXED_OBJECT, RUNTIME_COST_ACCOUNTER) + additionalPinnedClasses
|
val pinnedClasses: Set<String> = setOf(
|
||||||
|
SANDBOXED_OBJECT,
|
||||||
|
RuntimeCostAccounter.TYPE_NAME,
|
||||||
|
ruleViolationError,
|
||||||
|
thresholdViolationError
|
||||||
|
) + additionalPinnedClasses
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Functionality used to resolve the qualified name and relevant information about a class.
|
* Functionality used to resolve the qualified name and relevant information about a class.
|
||||||
*/
|
*/
|
||||||
val classResolver: ClassResolver = ClassResolver(pinnedClasses, whitelist, SANDBOX_PREFIX)
|
val classResolver: ClassResolver = ClassResolver(pinnedClasses, whitelist, SANDBOX_PREFIX)
|
||||||
|
|
||||||
|
private val bootstrapClassLoader = bootstrapJar?.let { BootstrapClassLoader(it, classResolver) }
|
||||||
|
val supportingClassLoader = SourceClassLoader(classPath, classResolver, bootstrapClassLoader)
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun close() {
|
||||||
|
supportingClassLoader.use {
|
||||||
|
bootstrapClassLoader?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* The package name prefix to use for classes loaded into a sandbox.
|
* The package name prefix to use for classes loaded into a sandbox.
|
||||||
*/
|
*/
|
||||||
private const val SANDBOX_PREFIX: String = "sandbox/"
|
private const val SANDBOX_PREFIX: String = "sandbox/"
|
||||||
|
|
||||||
private const val SANDBOXED_OBJECT = "sandbox/java/lang/Object"
|
private const val SANDBOXED_OBJECT = SANDBOX_PREFIX + "java/lang/Object"
|
||||||
private const val RUNTIME_COST_ACCOUNTER = RuntimeCostAccounter.TYPE_NAME
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package net.corda.djvm.analysis
|
package net.corda.djvm.analysis
|
||||||
|
|
||||||
|
import net.corda.djvm.code.asPackagePath
|
||||||
import net.corda.djvm.messages.MessageCollection
|
import net.corda.djvm.messages.MessageCollection
|
||||||
import net.corda.djvm.references.ClassHierarchy
|
import net.corda.djvm.references.ClassHierarchy
|
||||||
import net.corda.djvm.references.EntityReference
|
import net.corda.djvm.references.EntityReference
|
||||||
import net.corda.djvm.references.ReferenceMap
|
import net.corda.djvm.references.ReferenceMap
|
||||||
import net.corda.djvm.source.ClassSource
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The context in which one or more classes are analysed.
|
* The context in which one or more classes are analysed.
|
||||||
@ -13,13 +13,11 @@ import net.corda.djvm.source.ClassSource
|
|||||||
* @property classes List of class definitions that have been analyzed.
|
* @property classes List of class definitions that have been analyzed.
|
||||||
* @property references A collection of all referenced members found during analysis together with the locations from
|
* @property references A collection of all referenced members found during analysis together with the locations from
|
||||||
* where each member has been accessed or invoked.
|
* where each member has been accessed or invoked.
|
||||||
* @property inputClasses The classes passed in for analysis.
|
|
||||||
*/
|
*/
|
||||||
class AnalysisContext private constructor(
|
class AnalysisContext private constructor(
|
||||||
val messages: MessageCollection,
|
val messages: MessageCollection,
|
||||||
val classes: ClassHierarchy,
|
val classes: ClassHierarchy,
|
||||||
val references: ReferenceMap,
|
val references: ReferenceMap
|
||||||
val inputClasses: List<ClassSource>
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val origins = mutableMapOf<String, MutableSet<EntityReference>>()
|
private val origins = mutableMapOf<String, MutableSet<EntityReference>>()
|
||||||
@ -28,7 +26,7 @@ class AnalysisContext private constructor(
|
|||||||
* Record a class origin in the current analysis context.
|
* Record a class origin in the current analysis context.
|
||||||
*/
|
*/
|
||||||
fun recordClassOrigin(name: String, origin: EntityReference) {
|
fun recordClassOrigin(name: String, origin: EntityReference) {
|
||||||
origins.getOrPut(name.normalize()) { mutableSetOf() }.add(origin)
|
origins.getOrPut(name.asPackagePath) { mutableSetOf() }.add(origin)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,20 +40,14 @@ class AnalysisContext private constructor(
|
|||||||
/**
|
/**
|
||||||
* Create a new analysis context from provided configuration.
|
* Create a new analysis context from provided configuration.
|
||||||
*/
|
*/
|
||||||
fun fromConfiguration(configuration: AnalysisConfiguration, classes: List<ClassSource>): AnalysisContext {
|
fun fromConfiguration(configuration: AnalysisConfiguration): AnalysisContext {
|
||||||
return AnalysisContext(
|
return AnalysisContext(
|
||||||
MessageCollection(configuration.minimumSeverityLevel, configuration.prefixFilters),
|
MessageCollection(configuration.minimumSeverityLevel, configuration.prefixFilters),
|
||||||
ClassHierarchy(configuration.classModule, configuration.memberModule),
|
ClassHierarchy(configuration.classModule, configuration.memberModule),
|
||||||
ReferenceMap(configuration.classModule),
|
ReferenceMap(configuration.classModule)
|
||||||
classes
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Local extension method for normalizing a class name.
|
|
||||||
*/
|
|
||||||
private fun String.normalize() = this.replace("/", ".")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -4,30 +4,25 @@ import net.corda.djvm.code.EmitterModule
|
|||||||
import net.corda.djvm.code.Instruction
|
import net.corda.djvm.code.Instruction
|
||||||
import net.corda.djvm.code.instructions.*
|
import net.corda.djvm.code.instructions.*
|
||||||
import net.corda.djvm.messages.Message
|
import net.corda.djvm.messages.Message
|
||||||
import net.corda.djvm.references.ClassReference
|
import net.corda.djvm.references.*
|
||||||
import net.corda.djvm.references.ClassRepresentation
|
|
||||||
import net.corda.djvm.references.Member
|
|
||||||
import net.corda.djvm.references.MemberReference
|
|
||||||
import net.corda.djvm.source.SourceClassLoader
|
|
||||||
import org.objectweb.asm.*
|
import org.objectweb.asm.*
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Functionality for traversing a class and its members.
|
* Functionality for traversing a class and its members.
|
||||||
*
|
*
|
||||||
* @property classVisitor Class visitor to use when traversing the structure of classes.
|
|
||||||
* @property configuration The configuration to use for the analysis
|
* @property configuration The configuration to use for the analysis
|
||||||
|
* @property classVisitor Class visitor to use when traversing the structure of classes.
|
||||||
*/
|
*/
|
||||||
open class ClassAndMemberVisitor(
|
open class ClassAndMemberVisitor(
|
||||||
private val classVisitor: ClassVisitor? = null,
|
private val configuration: AnalysisConfiguration,
|
||||||
private val configuration: AnalysisConfiguration = AnalysisConfiguration()
|
private val classVisitor: ClassVisitor?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds a reference to the currently used analysis context.
|
* Holds a reference to the currently used analysis context.
|
||||||
*/
|
*/
|
||||||
protected var analysisContext: AnalysisContext =
|
protected var analysisContext: AnalysisContext = AnalysisContext.fromConfiguration(configuration)
|
||||||
AnalysisContext.fromConfiguration(configuration, emptyList())
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds a link to the class currently being traversed.
|
* Holds a link to the class currently being traversed.
|
||||||
@ -44,12 +39,6 @@ open class ClassAndMemberVisitor(
|
|||||||
*/
|
*/
|
||||||
private var sourceLocation = SourceLocation()
|
private var sourceLocation = SourceLocation()
|
||||||
|
|
||||||
/**
|
|
||||||
* The class loader used to find classes on the extended class path.
|
|
||||||
*/
|
|
||||||
private val supportingClassLoader =
|
|
||||||
SourceClassLoader(configuration.classPath, configuration.classResolver)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Analyze class by using the provided qualified name of the class.
|
* Analyze class by using the provided qualified name of the class.
|
||||||
*/
|
*/
|
||||||
@ -63,7 +52,7 @@ open class ClassAndMemberVisitor(
|
|||||||
* @param origin The originating class for the analysis.
|
* @param origin The originating class for the analysis.
|
||||||
*/
|
*/
|
||||||
fun analyze(className: String, context: AnalysisContext, origin: String? = null) {
|
fun analyze(className: String, context: AnalysisContext, origin: String? = null) {
|
||||||
supportingClassLoader.classReader(className, context, origin).apply {
|
configuration.supportingClassLoader.classReader(className, context, origin).apply {
|
||||||
analyze(this, context)
|
analyze(this, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,7 +156,8 @@ open class ClassAndMemberVisitor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run action with a guard that populates [messages] based on the output.
|
* Run action with a guard that populates [AnalysisRuntimeContext.messages]
|
||||||
|
* based on the output.
|
||||||
*/
|
*/
|
||||||
private inline fun captureExceptions(action: () -> Unit): Boolean {
|
private inline fun captureExceptions(action: () -> Unit): Boolean {
|
||||||
return try {
|
return try {
|
||||||
@ -229,9 +219,7 @@ open class ClassAndMemberVisitor(
|
|||||||
ClassRepresentation(version, access, name, superClassName, interfaceNames, genericsDetails = signature ?: "").also {
|
ClassRepresentation(version, access, name, superClassName, interfaceNames, genericsDetails = signature ?: "").also {
|
||||||
currentClass = it
|
currentClass = it
|
||||||
currentMember = null
|
currentMember = null
|
||||||
sourceLocation = SourceLocation(
|
sourceLocation = SourceLocation(className = name)
|
||||||
className = name
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
captureExceptions {
|
captureExceptions {
|
||||||
currentClass = visitClass(currentClass!!)
|
currentClass = visitClass(currentClass!!)
|
||||||
@ -251,7 +239,7 @@ open class ClassAndMemberVisitor(
|
|||||||
override fun visitEnd() {
|
override fun visitEnd() {
|
||||||
configuration.classModule
|
configuration.classModule
|
||||||
.getClassReferencesFromClass(currentClass!!, configuration.analyzeAnnotations)
|
.getClassReferencesFromClass(currentClass!!, configuration.analyzeAnnotations)
|
||||||
.forEach { recordTypeReference(it) }
|
.forEach(::recordTypeReference)
|
||||||
captureExceptions {
|
captureExceptions {
|
||||||
visitClassEnd(currentClass!!)
|
visitClassEnd(currentClass!!)
|
||||||
}
|
}
|
||||||
@ -306,14 +294,15 @@ open class ClassAndMemberVisitor(
|
|||||||
configuration.memberModule.addToClass(clazz, visitedMember ?: member)
|
configuration.memberModule.addToClass(clazz, visitedMember ?: member)
|
||||||
return if (processMember) {
|
return if (processMember) {
|
||||||
val derivedMember = visitedMember ?: member
|
val derivedMember = visitedMember ?: member
|
||||||
val targetVisitor = super.visitMethod(
|
super.visitMethod(
|
||||||
derivedMember.access,
|
derivedMember.access,
|
||||||
derivedMember.memberName,
|
derivedMember.memberName,
|
||||||
derivedMember.signature,
|
derivedMember.signature,
|
||||||
signature,
|
signature,
|
||||||
derivedMember.exceptions.toTypedArray()
|
derivedMember.exceptions.toTypedArray()
|
||||||
)
|
)?.let { targetVisitor ->
|
||||||
MethodVisitorImpl(targetVisitor)
|
MethodVisitorImpl(targetVisitor, derivedMember)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@ -340,14 +329,15 @@ open class ClassAndMemberVisitor(
|
|||||||
configuration.memberModule.addToClass(clazz, visitedMember ?: member)
|
configuration.memberModule.addToClass(clazz, visitedMember ?: member)
|
||||||
return if (processMember) {
|
return if (processMember) {
|
||||||
val derivedMember = visitedMember ?: member
|
val derivedMember = visitedMember ?: member
|
||||||
val targetVisitor = super.visitField(
|
super.visitField(
|
||||||
derivedMember.access,
|
derivedMember.access,
|
||||||
derivedMember.memberName,
|
derivedMember.memberName,
|
||||||
derivedMember.signature,
|
derivedMember.signature,
|
||||||
signature,
|
signature,
|
||||||
derivedMember.value
|
derivedMember.value
|
||||||
)
|
)?.let { targetVisitor ->
|
||||||
FieldVisitorImpl(targetVisitor)
|
FieldVisitorImpl(targetVisitor)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@ -359,7 +349,8 @@ open class ClassAndMemberVisitor(
|
|||||||
* Visitor used to traverse and analyze a method.
|
* Visitor used to traverse and analyze a method.
|
||||||
*/
|
*/
|
||||||
private inner class MethodVisitorImpl(
|
private inner class MethodVisitorImpl(
|
||||||
targetVisitor: MethodVisitor?
|
targetVisitor: MethodVisitor,
|
||||||
|
private val method: Member
|
||||||
) : MethodVisitor(API_VERSION, targetVisitor) {
|
) : MethodVisitor(API_VERSION, targetVisitor) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -387,6 +378,16 @@ open class ClassAndMemberVisitor(
|
|||||||
return super.visitAnnotation(desc, visible)
|
return super.visitAnnotation(desc, visible)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write any new method body code, assuming the definition providers
|
||||||
|
* have provided any. This handler will not be visited if this method
|
||||||
|
* has no existing code.
|
||||||
|
*/
|
||||||
|
override fun visitCode() {
|
||||||
|
tryReplaceMethodBody()
|
||||||
|
super.visitCode()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract information about provided field access instruction.
|
* Extract information about provided field access instruction.
|
||||||
*/
|
*/
|
||||||
@ -493,6 +494,29 @@ open class ClassAndMemberVisitor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finish visiting this method, writing any new method body byte-code
|
||||||
|
* if we haven't written it already. This would (presumably) only happen
|
||||||
|
* for methods that previously had no body, e.g. native methods.
|
||||||
|
*/
|
||||||
|
override fun visitEnd() {
|
||||||
|
tryReplaceMethodBody()
|
||||||
|
super.visitEnd()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tryReplaceMethodBody() {
|
||||||
|
if (method.body.isNotEmpty() && (mv != null)) {
|
||||||
|
EmitterModule(mv).apply {
|
||||||
|
for (body in method.body) {
|
||||||
|
body(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mv.visitMaxs(-1, -1)
|
||||||
|
mv.visitEnd()
|
||||||
|
mv = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function used to streamline the access to an instruction and to catch any related processing errors.
|
* Helper function used to streamline the access to an instruction and to catch any related processing errors.
|
||||||
*/
|
*/
|
||||||
@ -517,7 +541,7 @@ open class ClassAndMemberVisitor(
|
|||||||
* Visitor used to traverse and analyze a field.
|
* Visitor used to traverse and analyze a field.
|
||||||
*/
|
*/
|
||||||
private inner class FieldVisitorImpl(
|
private inner class FieldVisitorImpl(
|
||||||
targetVisitor: FieldVisitor?
|
targetVisitor: FieldVisitor
|
||||||
) : FieldVisitor(API_VERSION, targetVisitor) {
|
) : FieldVisitor(API_VERSION, targetVisitor) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package net.corda.djvm.analysis
|
package net.corda.djvm.analysis
|
||||||
|
|
||||||
|
import net.corda.djvm.code.asPackagePath
|
||||||
|
import net.corda.djvm.code.asResourcePath
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Functionality for resolving the class name of a sandboxable class.
|
* Functionality for resolving the class name of a sandboxable class.
|
||||||
*
|
*
|
||||||
@ -32,12 +35,12 @@ class ClassResolver(
|
|||||||
*/
|
*/
|
||||||
fun resolve(name: String): String {
|
fun resolve(name: String): String {
|
||||||
return when {
|
return when {
|
||||||
name.startsWith("[") && name.endsWith(";") -> {
|
name.startsWith('[') && name.endsWith(';') -> {
|
||||||
complexArrayTypeRegex.replace(name) {
|
complexArrayTypeRegex.replace(name) {
|
||||||
"${it.groupValues[1]}L${resolveName(it.groupValues[2])};"
|
"${it.groupValues[1]}L${resolveName(it.groupValues[2])};"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
name.startsWith("[") && !name.endsWith(";") -> name
|
name.startsWith('[') && !name.endsWith(';') -> name
|
||||||
else -> resolveName(name)
|
else -> resolveName(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,7 +49,7 @@ class ClassResolver(
|
|||||||
* Resolve the class name from a fully qualified normalized name.
|
* Resolve the class name from a fully qualified normalized name.
|
||||||
*/
|
*/
|
||||||
fun resolveNormalized(name: String): String {
|
fun resolveNormalized(name: String): String {
|
||||||
return resolve(name.replace('.', '/')).replace('/', '.')
|
return resolve(name.asResourcePath).asPackagePath
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -96,7 +99,7 @@ class ClassResolver(
|
|||||||
* Reverse the resolution of a class name from a fully qualified normalized name.
|
* Reverse the resolution of a class name from a fully qualified normalized name.
|
||||||
*/
|
*/
|
||||||
fun reverseNormalized(name: String): String {
|
fun reverseNormalized(name: String): String {
|
||||||
return reverse(name.replace('.', '/')).replace('/', '.')
|
return reverse(name.asResourcePath).asPackagePath
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -117,7 +117,8 @@ open class Whitelist private constructor(
|
|||||||
"^java/lang/Throwable(\\..*)?$".toRegex(),
|
"^java/lang/Throwable(\\..*)?$".toRegex(),
|
||||||
"^java/lang/Void(\\..*)?$".toRegex(),
|
"^java/lang/Void(\\..*)?$".toRegex(),
|
||||||
"^java/lang/.*Error(\\..*)?$".toRegex(),
|
"^java/lang/.*Error(\\..*)?$".toRegex(),
|
||||||
"^java/lang/.*Exception(\\..*)?$".toRegex()
|
"^java/lang/.*Exception(\\..*)?$".toRegex(),
|
||||||
|
"^java/lang/reflect/Array(\\..*)?$".toRegex()
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,7 +20,7 @@ class ClassMutator(
|
|||||||
private val configuration: AnalysisConfiguration,
|
private val configuration: AnalysisConfiguration,
|
||||||
private val definitionProviders: List<DefinitionProvider> = emptyList(),
|
private val definitionProviders: List<DefinitionProvider> = emptyList(),
|
||||||
private val emitters: List<Emitter> = emptyList()
|
private val emitters: List<Emitter> = emptyList()
|
||||||
) : ClassAndMemberVisitor(classVisitor, configuration = configuration) {
|
) : ClassAndMemberVisitor(configuration, classVisitor) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks whether any modifications have been applied to any of the processed class(es) and pertinent members.
|
* Tracks whether any modifications have been applied to any of the processed class(es) and pertinent members.
|
||||||
@ -82,7 +82,8 @@ class ClassMutator(
|
|||||||
*/
|
*/
|
||||||
override fun visitInstruction(method: Member, emitter: EmitterModule, instruction: Instruction) {
|
override fun visitInstruction(method: Member, emitter: EmitterModule, instruction: Instruction) {
|
||||||
val context = EmitterContext(currentAnalysisContext(), configuration, emitter)
|
val context = EmitterContext(currentAnalysisContext(), configuration, emitter)
|
||||||
Processor.processEntriesOfType<Emitter>(emitters, analysisContext.messages) {
|
// We need to apply the tracing emitters before the non-tracing ones.
|
||||||
|
Processor.processEntriesOfType<Emitter>(emitters.sortedByDescending(Emitter::isTracer), analysisContext.messages) {
|
||||||
it.emit(context, instruction)
|
it.emit(context, instruction)
|
||||||
}
|
}
|
||||||
if (!emitter.emitDefaultInstruction || emitter.hasEmittedCustomCode) {
|
if (!emitter.emitDefaultInstruction || emitter.hasEmittedCustomCode) {
|
||||||
|
@ -20,6 +20,7 @@ interface Emitter {
|
|||||||
/**
|
/**
|
||||||
* Indication of whether or not the emitter performs instrumentation for tracing inside the sandbox.
|
* Indication of whether or not the emitter performs instrumentation for tracing inside the sandbox.
|
||||||
*/
|
*/
|
||||||
|
@JvmDefault
|
||||||
val isTracer: Boolean
|
val isTracer: Boolean
|
||||||
get() = false
|
get() = false
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package net.corda.djvm.code
|
package net.corda.djvm.code
|
||||||
|
|
||||||
|
import org.objectweb.asm.Label
|
||||||
import org.objectweb.asm.MethodVisitor
|
import org.objectweb.asm.MethodVisitor
|
||||||
import org.objectweb.asm.Opcodes
|
import org.objectweb.asm.Opcodes.*
|
||||||
|
import org.objectweb.asm.Type
|
||||||
import sandbox.net.corda.djvm.costing.RuntimeCostAccounter
|
import sandbox.net.corda.djvm.costing.RuntimeCostAccounter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,7 +31,7 @@ class EmitterModule(
|
|||||||
/**
|
/**
|
||||||
* Emit instruction for creating a new object of type [typeName].
|
* Emit instruction for creating a new object of type [typeName].
|
||||||
*/
|
*/
|
||||||
fun new(typeName: String, opcode: Int = Opcodes.NEW) {
|
fun new(typeName: String, opcode: Int = NEW) {
|
||||||
hasEmittedCustomCode = true
|
hasEmittedCustomCode = true
|
||||||
methodVisitor.visitTypeInsn(opcode, typeName)
|
methodVisitor.visitTypeInsn(opcode, typeName)
|
||||||
}
|
}
|
||||||
@ -38,7 +40,7 @@ class EmitterModule(
|
|||||||
* Emit instruction for creating a new object of type [T].
|
* Emit instruction for creating a new object of type [T].
|
||||||
*/
|
*/
|
||||||
inline fun <reified T> new() {
|
inline fun <reified T> new() {
|
||||||
new(T::class.java.name)
|
new(Type.getInternalName(T::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,7 +64,7 @@ class EmitterModule(
|
|||||||
*/
|
*/
|
||||||
fun invokeStatic(owner: String, name: String, descriptor: String, isInterface: Boolean = false) {
|
fun invokeStatic(owner: String, name: String, descriptor: String, isInterface: Boolean = false) {
|
||||||
hasEmittedCustomCode = true
|
hasEmittedCustomCode = true
|
||||||
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, descriptor, isInterface)
|
methodVisitor.visitMethodInsn(INVOKESTATIC, owner, name, descriptor, isInterface)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -70,14 +72,14 @@ class EmitterModule(
|
|||||||
*/
|
*/
|
||||||
fun invokeSpecial(owner: String, name: String, descriptor: String, isInterface: Boolean = false) {
|
fun invokeSpecial(owner: String, name: String, descriptor: String, isInterface: Boolean = false) {
|
||||||
hasEmittedCustomCode = true
|
hasEmittedCustomCode = true
|
||||||
methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, descriptor, isInterface)
|
methodVisitor.visitMethodInsn(INVOKESPECIAL, owner, name, descriptor, isInterface)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emit instruction for invoking a special method on class [T], e.g. a constructor or a method on a super-type.
|
* Emit instruction for invoking a special method on class [T], e.g. a constructor or a method on a super-type.
|
||||||
*/
|
*/
|
||||||
inline fun <reified T> invokeSpecial(name: String, descriptor: String, isInterface: Boolean = false) {
|
inline fun <reified T> invokeSpecial(name: String, descriptor: String, isInterface: Boolean = false) {
|
||||||
invokeSpecial(T::class.java.name, name, descriptor, isInterface)
|
invokeSpecial(Type.getInternalName(T::class.java), name, descriptor, isInterface)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -85,7 +87,7 @@ class EmitterModule(
|
|||||||
*/
|
*/
|
||||||
fun pop() {
|
fun pop() {
|
||||||
hasEmittedCustomCode = true
|
hasEmittedCustomCode = true
|
||||||
methodVisitor.visitInsn(Opcodes.POP)
|
methodVisitor.visitInsn(POP)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -93,19 +95,40 @@ class EmitterModule(
|
|||||||
*/
|
*/
|
||||||
fun duplicate() {
|
fun duplicate() {
|
||||||
hasEmittedCustomCode = true
|
hasEmittedCustomCode = true
|
||||||
methodVisitor.visitInsn(Opcodes.DUP)
|
methodVisitor.visitInsn(DUP)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emit a sequence of instructions for instantiating and throwing an exception based on the provided message.
|
* Emit a sequence of instructions for instantiating and throwing an exception based on the provided message.
|
||||||
*/
|
*/
|
||||||
fun throwError(message: String) {
|
fun <T : Throwable> throwException(exceptionType: Class<T>, message: String) {
|
||||||
hasEmittedCustomCode = true
|
hasEmittedCustomCode = true
|
||||||
new<java.lang.Exception>()
|
val exceptionName = Type.getInternalName(exceptionType)
|
||||||
methodVisitor.visitInsn(Opcodes.DUP)
|
new(exceptionName)
|
||||||
|
methodVisitor.visitInsn(DUP)
|
||||||
methodVisitor.visitLdcInsn(message)
|
methodVisitor.visitLdcInsn(message)
|
||||||
invokeSpecial<java.lang.Exception>("<init>", "(Ljava/lang/String;)V")
|
invokeSpecial(exceptionName, "<init>", "(Ljava/lang/String;)V")
|
||||||
methodVisitor.visitInsn(Opcodes.ATHROW)
|
methodVisitor.visitInsn(ATHROW)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : Throwable> throwException(message: String) = throwException(T::class.java, message)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit instruction for returning from "void" method.
|
||||||
|
*/
|
||||||
|
fun returnVoid() {
|
||||||
|
methodVisitor.visitInsn(RETURN)
|
||||||
|
hasEmittedCustomCode = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit instructions for a new line number.
|
||||||
|
*/
|
||||||
|
fun lineNumber(line: Int) {
|
||||||
|
val label = Label()
|
||||||
|
methodVisitor.visitLabel(label)
|
||||||
|
methodVisitor.visitLineNumber(line, label)
|
||||||
|
hasEmittedCustomCode = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
15
djvm/src/main/kotlin/net/corda/djvm/code/Types.kt
Normal file
15
djvm/src/main/kotlin/net/corda/djvm/code/Types.kt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
@file:JvmName("Types")
|
||||||
|
package net.corda.djvm.code
|
||||||
|
|
||||||
|
import org.objectweb.asm.Type
|
||||||
|
import sandbox.net.corda.djvm.costing.ThresholdViolationError
|
||||||
|
import sandbox.net.corda.djvm.rules.RuleViolationError
|
||||||
|
|
||||||
|
val ruleViolationError: String = Type.getInternalName(RuleViolationError::class.java)
|
||||||
|
val thresholdViolationError: String = Type.getInternalName(ThresholdViolationError::class.java)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local extension method for normalizing a class name.
|
||||||
|
*/
|
||||||
|
val String.asPackagePath: String get() = this.replace('/', '.')
|
||||||
|
val String.asResourcePath: String get() = this.replace('.', '/')
|
@ -1,6 +1,7 @@
|
|||||||
package net.corda.djvm.costing
|
package net.corda.djvm.costing
|
||||||
|
|
||||||
import net.corda.djvm.utilities.loggerFor
|
import net.corda.djvm.utilities.loggerFor
|
||||||
|
import sandbox.net.corda.djvm.costing.ThresholdViolationError
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cost metric to be used in a sandbox environment. The metric has a threshold and a mechanism for reporting violations.
|
* Cost metric to be used in a sandbox environment. The metric has a threshold and a mechanism for reporting violations.
|
||||||
@ -41,7 +42,7 @@ open class TypedRuntimeCost<T>(
|
|||||||
if (thresholdPredicate(newValue)) {
|
if (thresholdPredicate(newValue)) {
|
||||||
val message = errorMessage(currentThread)
|
val message = errorMessage(currentThread)
|
||||||
logger.error("Threshold breached; {}", message)
|
logger.error("Threshold breached; {}", message)
|
||||||
throw ThresholdViolationException(message)
|
throw ThresholdViolationError(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package net.corda.djvm.execution
|
|||||||
|
|
||||||
import net.corda.djvm.SandboxConfiguration
|
import net.corda.djvm.SandboxConfiguration
|
||||||
import net.corda.djvm.source.ClassSource
|
import net.corda.djvm.source.ClassSource
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The executor is responsible for spinning up a deterministic, sandboxed environment and launching the referenced code
|
* The executor is responsible for spinning up a deterministic, sandboxed environment and launching the referenced code
|
||||||
@ -12,14 +13,14 @@ import net.corda.djvm.source.ClassSource
|
|||||||
* @param configuration The configuration of the sandbox.
|
* @param configuration The configuration of the sandbox.
|
||||||
*/
|
*/
|
||||||
class DeterministicSandboxExecutor<TInput, TOutput>(
|
class DeterministicSandboxExecutor<TInput, TOutput>(
|
||||||
configuration: SandboxConfiguration = SandboxConfiguration.DEFAULT
|
configuration: SandboxConfiguration
|
||||||
) : SandboxExecutor<TInput, TOutput>(configuration) {
|
) : SandboxExecutor<TInput, TOutput>(configuration) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Short-hand for running a [SandboxedRunnable] in a sandbox by its type reference.
|
* Short-hand for running a [Function] in a sandbox by its type reference.
|
||||||
*/
|
*/
|
||||||
inline fun <reified TRunnable : SandboxedRunnable<TInput, TOutput>> run(input: TInput):
|
inline fun <reified TRunnable : Function<in TInput, out TOutput>> run(input: TInput):
|
||||||
ExecutionSummaryWithResult<TOutput?> {
|
ExecutionSummaryWithResult<TOutput> {
|
||||||
return run(ClassSource.fromClassName(TRunnable::class.java.name), input)
|
return run(ClassSource.fromClassName(TRunnable::class.java.name), input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
package net.corda.djvm.execution
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Functionality runnable by a sandbox executor, marked for discoverability.
|
|
||||||
*/
|
|
||||||
interface DiscoverableRunnable
|
|
@ -1,7 +1,7 @@
|
|||||||
package net.corda.djvm.execution
|
package net.corda.djvm.execution
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The execution profile of a [SandboxedRunnable] when run in a sandbox.
|
* The execution profile of a [java.util.function.Function] when run in a sandbox.
|
||||||
*
|
*
|
||||||
* @property allocationCostThreshold The threshold placed on allocations.
|
* @property allocationCostThreshold The threshold placed on allocations.
|
||||||
* @property invocationCostThreshold The threshold placed on invocations.
|
* @property invocationCostThreshold The threshold placed on invocations.
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package net.corda.djvm.execution
|
package net.corda.djvm.execution
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The summary of the execution of a [SandboxedRunnable] in a sandbox. This class has no representation of the outcome,
|
* The summary of the execution of a [java.util.function.Function] in a sandbox. This class has no representation of the
|
||||||
* and is typically used when there has been a pre-mature exit from the sandbox, for instance, if an exception was
|
* outcome, and is typically used when there has been a pre-mature exit from the sandbox, for instance, if an exception
|
||||||
* thrown.
|
* was thrown.
|
||||||
*
|
*
|
||||||
* @property costs The costs accumulated when running the sandboxed code.
|
* @property costs The costs accumulated when running the sandboxed code.
|
||||||
*/
|
*/
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.corda.djvm.execution
|
package net.corda.djvm.execution
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The summary of the execution of a [SandboxedRunnable] in a sandbox.
|
* The summary of the execution of a [java.util.function.Function] in a sandbox.
|
||||||
*
|
*
|
||||||
* @property result The outcome of the sandboxed operation.
|
* @property result The outcome of the sandboxed operation.
|
||||||
* @see ExecutionSummary
|
* @see ExecutionSummary
|
||||||
|
@ -2,7 +2,6 @@ package net.corda.djvm.execution
|
|||||||
|
|
||||||
import net.corda.djvm.SandboxConfiguration
|
import net.corda.djvm.SandboxConfiguration
|
||||||
import net.corda.djvm.SandboxRuntimeContext
|
import net.corda.djvm.SandboxRuntimeContext
|
||||||
import net.corda.djvm.analysis.AnalysisContext
|
|
||||||
import net.corda.djvm.messages.MessageCollection
|
import net.corda.djvm.messages.MessageCollection
|
||||||
import net.corda.djvm.rewiring.SandboxClassLoader
|
import net.corda.djvm.rewiring.SandboxClassLoader
|
||||||
import net.corda.djvm.rewiring.SandboxClassLoadingException
|
import net.corda.djvm.rewiring.SandboxClassLoadingException
|
||||||
@ -16,8 +15,7 @@ import kotlin.concurrent.thread
|
|||||||
*/
|
*/
|
||||||
class IsolatedTask(
|
class IsolatedTask(
|
||||||
private val identifier: String,
|
private val identifier: String,
|
||||||
private val configuration: SandboxConfiguration,
|
private val configuration: SandboxConfiguration
|
||||||
private val context: AnalysisContext
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,12 +30,12 @@ class IsolatedTask(
|
|||||||
var exception: Throwable? = null
|
var exception: Throwable? = null
|
||||||
thread(name = threadName, isDaemon = true) {
|
thread(name = threadName, isDaemon = true) {
|
||||||
logger.trace("Entering isolated runtime environment...")
|
logger.trace("Entering isolated runtime environment...")
|
||||||
SandboxRuntimeContext(configuration, context.inputClasses).use {
|
SandboxRuntimeContext(configuration).use {
|
||||||
output = try {
|
output = try {
|
||||||
action(runnable)
|
action(runnable)
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
logger.error("Exception caught in isolated runtime environment", ex)
|
logger.error("Exception caught in isolated runtime environment", ex)
|
||||||
exception = ex
|
exception = (ex as? LinkageError)?.cause ?: ex
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
costs = CostSummary(
|
costs = CostSummary(
|
||||||
@ -84,7 +82,7 @@ class IsolatedTask(
|
|||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The class loader to use for loading the [SandboxedRunnable] and any referenced code in [SandboxExecutor.run].
|
* The class loader to use for loading the [java.util.function.Function] and any referenced code in [SandboxExecutor.run].
|
||||||
*/
|
*/
|
||||||
val classLoader: SandboxClassLoader
|
val classLoader: SandboxClassLoader
|
||||||
get() = SandboxRuntimeContext.instance.classLoader
|
get() = SandboxRuntimeContext.instance.classLoader
|
||||||
|
@ -11,7 +11,6 @@ import net.corda.djvm.rewiring.SandboxClassLoadingException
|
|||||||
import net.corda.djvm.source.ClassSource
|
import net.corda.djvm.source.ClassSource
|
||||||
import net.corda.djvm.utilities.loggerFor
|
import net.corda.djvm.utilities.loggerFor
|
||||||
import net.corda.djvm.validation.ReferenceValidationSummary
|
import net.corda.djvm.validation.ReferenceValidationSummary
|
||||||
import net.corda.djvm.validation.ReferenceValidator
|
|
||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,7 +21,7 @@ import java.lang.reflect.InvocationTargetException
|
|||||||
* @property configuration The configuration of sandbox.
|
* @property configuration The configuration of sandbox.
|
||||||
*/
|
*/
|
||||||
open class SandboxExecutor<in TInput, out TOutput>(
|
open class SandboxExecutor<in TInput, out TOutput>(
|
||||||
protected val configuration: SandboxConfiguration = SandboxConfiguration.DEFAULT
|
protected val configuration: SandboxConfiguration
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val classModule = configuration.analysisConfiguration.classModule
|
private val classModule = configuration.analysisConfiguration.classModule
|
||||||
@ -32,12 +31,7 @@ open class SandboxExecutor<in TInput, out TOutput>(
|
|||||||
private val whitelist = configuration.analysisConfiguration.whitelist
|
private val whitelist = configuration.analysisConfiguration.whitelist
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module used to validate all traversable references before instantiating and executing a [SandboxedRunnable].
|
* Executes a [java.util.function.Function] implementation.
|
||||||
*/
|
|
||||||
private val referenceValidator = ReferenceValidator(configuration.analysisConfiguration)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes a [SandboxedRunnable] implementation.
|
|
||||||
*
|
*
|
||||||
* @param runnableClass The entry point of the sandboxed code to run.
|
* @param runnableClass The entry point of the sandboxed code to run.
|
||||||
* @param input The input to provide to the sandboxed environment.
|
* @param input The input to provide to the sandboxed environment.
|
||||||
@ -50,7 +44,7 @@ open class SandboxExecutor<in TInput, out TOutput>(
|
|||||||
open fun run(
|
open fun run(
|
||||||
runnableClass: ClassSource,
|
runnableClass: ClassSource,
|
||||||
input: TInput
|
input: TInput
|
||||||
): ExecutionSummaryWithResult<TOutput?> {
|
): ExecutionSummaryWithResult<TOutput> {
|
||||||
// 1. We first do a breath first traversal of the class hierarchy, starting from the requested class.
|
// 1. We first do a breath first traversal of the class hierarchy, starting from the requested class.
|
||||||
// The branching is defined by class references from referencesFromLocation.
|
// The branching is defined by class references from referencesFromLocation.
|
||||||
// 2. For each class we run validation against defined rules.
|
// 2. For each class we run validation against defined rules.
|
||||||
@ -63,22 +57,22 @@ open class SandboxExecutor<in TInput, out TOutput>(
|
|||||||
// 6. For execution, we then load the top-level class, implementing the SandboxedRunnable interface, again and
|
// 6. For execution, we then load the top-level class, implementing the SandboxedRunnable interface, again and
|
||||||
// and consequently hit the cache. Once loaded, we can execute the code on the spawned thread, i.e., in an
|
// and consequently hit the cache. Once loaded, we can execute the code on the spawned thread, i.e., in an
|
||||||
// isolated environment.
|
// isolated environment.
|
||||||
logger.trace("Executing {} with input {}...", runnableClass, input)
|
logger.debug("Executing {} with input {}...", runnableClass, input)
|
||||||
// TODO Class sources can be analyzed in parallel, although this require making the analysis context thread-safe
|
// TODO Class sources can be analyzed in parallel, although this require making the analysis context thread-safe
|
||||||
// To do so, one could start by batching the first X classes from the class sources and analyse each one in
|
// To do so, one could start by batching the first X classes from the class sources and analyse each one in
|
||||||
// parallel, caching any intermediate state and subsequently process enqueued sources in parallel batches as well.
|
// parallel, caching any intermediate state and subsequently process enqueued sources in parallel batches as well.
|
||||||
// Note that this would require some rework of the [IsolatedTask] and the class loader to bypass the limitation
|
// Note that this would require some rework of the [IsolatedTask] and the class loader to bypass the limitation
|
||||||
// of caching and state preserved in thread-local contexts.
|
// of caching and state preserved in thread-local contexts.
|
||||||
val classSources = listOf(runnableClass)
|
val classSources = listOf(runnableClass)
|
||||||
val context = AnalysisContext.fromConfiguration(configuration.analysisConfiguration, classSources)
|
val context = AnalysisContext.fromConfiguration(configuration.analysisConfiguration)
|
||||||
val result = IsolatedTask(runnableClass.qualifiedClassName, configuration, context).run {
|
val result = IsolatedTask(runnableClass.qualifiedClassName, configuration).run {
|
||||||
validate(context, classLoader, classSources)
|
validate(context, classLoader, classSources)
|
||||||
val loadedClass = classLoader.loadClassAndBytes(runnableClass, context)
|
val loadedClass = classLoader.loadClassAndBytes(runnableClass, context)
|
||||||
val instance = loadedClass.type.newInstance()
|
val instance = loadedClass.type.newInstance()
|
||||||
val method = loadedClass.type.getMethod("run", Any::class.java)
|
val method = loadedClass.type.getMethod("apply", Any::class.java)
|
||||||
try {
|
try {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
method.invoke(instance, input) as? TOutput?
|
method.invoke(instance, input) as? TOutput
|
||||||
} catch (ex: InvocationTargetException) {
|
} catch (ex: InvocationTargetException) {
|
||||||
throw ex.targetException
|
throw ex.targetException
|
||||||
}
|
}
|
||||||
@ -105,8 +99,8 @@ open class SandboxExecutor<in TInput, out TOutput>(
|
|||||||
* @return A [LoadedClass] with the class' byte code, type and name.
|
* @return A [LoadedClass] with the class' byte code, type and name.
|
||||||
*/
|
*/
|
||||||
fun load(classSource: ClassSource): LoadedClass {
|
fun load(classSource: ClassSource): LoadedClass {
|
||||||
val context = AnalysisContext.fromConfiguration(configuration.analysisConfiguration, listOf(classSource))
|
val context = AnalysisContext.fromConfiguration(configuration.analysisConfiguration)
|
||||||
val result = IsolatedTask("LoadClass", configuration, context).run {
|
val result = IsolatedTask("LoadClass", configuration).run {
|
||||||
classLoader.loadClassAndBytes(classSource, context)
|
classLoader.loadClassAndBytes(classSource, context)
|
||||||
}
|
}
|
||||||
return result.output ?: throw ClassNotFoundException(classSource.qualifiedClassName)
|
return result.output ?: throw ClassNotFoundException(classSource.qualifiedClassName)
|
||||||
@ -125,8 +119,8 @@ open class SandboxExecutor<in TInput, out TOutput>(
|
|||||||
@Throws(SandboxClassLoadingException::class)
|
@Throws(SandboxClassLoadingException::class)
|
||||||
fun validate(vararg classSources: ClassSource): ReferenceValidationSummary {
|
fun validate(vararg classSources: ClassSource): ReferenceValidationSummary {
|
||||||
logger.trace("Validating {}...", classSources)
|
logger.trace("Validating {}...", classSources)
|
||||||
val context = AnalysisContext.fromConfiguration(configuration.analysisConfiguration, classSources.toList())
|
val context = AnalysisContext.fromConfiguration(configuration.analysisConfiguration)
|
||||||
val result = IsolatedTask("Validation", configuration, context).run {
|
val result = IsolatedTask("Validation", configuration).run {
|
||||||
validate(context, classLoader, classSources.toList())
|
validate(context, classLoader, classSources.toList())
|
||||||
}
|
}
|
||||||
logger.trace("Validation of {} resulted in {}", classSources, result)
|
logger.trace("Validation of {} resulted in {}", classSources, result)
|
||||||
@ -172,10 +166,6 @@ open class SandboxExecutor<in TInput, out TOutput>(
|
|||||||
}
|
}
|
||||||
failOnReportedErrorsInContext(context)
|
failOnReportedErrorsInContext(context)
|
||||||
|
|
||||||
// Validate all references in class hierarchy before proceeding.
|
|
||||||
referenceValidator.validate(context, classLoader.analyzer)
|
|
||||||
failOnReportedErrorsInContext(context)
|
|
||||||
|
|
||||||
return ReferenceValidationSummary(context.classes, context.messages, context.classOrigins)
|
return ReferenceValidationSummary(context.classes, context.messages, context.classOrigins)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,7 +175,7 @@ open class SandboxExecutor<in TInput, out TOutput>(
|
|||||||
private inline fun processClassQueue(
|
private inline fun processClassQueue(
|
||||||
vararg elements: ClassSource, action: QueueProcessor<ClassSource>.(ClassSource, String) -> Unit
|
vararg elements: ClassSource, action: QueueProcessor<ClassSource>.(ClassSource, String) -> Unit
|
||||||
) {
|
) {
|
||||||
QueueProcessor({ it.qualifiedClassName }, *elements).process { classSource ->
|
QueueProcessor(ClassSource::qualifiedClassName, *elements).process { classSource ->
|
||||||
val className = classResolver.reverse(classModule.getBinaryClassName(classSource.qualifiedClassName))
|
val className = classResolver.reverse(classModule.getBinaryClassName(classSource.qualifiedClassName))
|
||||||
if (!whitelist.matches(className)) {
|
if (!whitelist.matches(className)) {
|
||||||
action(classSource, className)
|
action(classSource, className)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user