Merge pull request #7 from corda/tudor_merge_os_master

Tudor merge os master
This commit is contained in:
Tudor Malene 2018-10-02 17:16:17 +01:00 committed by GitHub
commit 020a292383
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
510 changed files with 10259 additions and 5920 deletions

View File

@ -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
## ##

View 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"

View File

@ -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)

View File

@ -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
View 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
}
}

View File

@ -1,2 +1,4 @@
rootProject.name = 'buildSrc' rootProject.name = 'buildSrc'
include 'canonicalizer' include 'canonicalizer'
apply from: '../buildCacheSettings.gradle'

View File

@ -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)

View File

@ -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")

View File

@ -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()));
} }

View File

@ -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())

View File

@ -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))
} }
/** /**

View File

@ -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)

View File

@ -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(
@ -16,20 +12,3 @@ fun createCordaRPCClientWithSslAndClassLoader(
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 { }
}

View File

@ -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))

View File

@ -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) {
try {
currentThrowable.initCause(callSite) 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?,

View File

@ -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)
} }

View File

@ -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() {}

View File

@ -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()

View File

@ -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() } }

View File

@ -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 }
} }

View File

@ -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)
} }

View File

@ -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)

View File

@ -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">

View File

@ -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

View File

@ -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

View File

@ -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
}
}

View File

@ -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
} }

View File

@ -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
}
}
}

View File

@ -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"

View File

@ -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

View File

@ -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.*

View File

@ -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.*

View File

@ -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()
}

View File

@ -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")
}
}

View 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
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()
}

View File

@ -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
}
} }

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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);

View File

@ -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. */

View File

@ -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)
} }

View File

@ -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

View File

@ -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)

View File

@ -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 }
}
} }
} }

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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(),

View File

@ -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)

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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
} }
/** /**

View File

@ -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)
}

View File

@ -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 }
} }
} }

View File

@ -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,7 +156,8 @@ 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] val group = filteredSerialisedComponents[componentGroupIndex]
// Because the filter passed, we know there is a match. We also use first Vs single as the init function // Because the filter passed, we know there is a match. We also use first Vs single as the init function
// of WireTransaction ensures there are no duplicated groups. // of WireTransaction ensures there are no duplicated groups.
@ -166,9 +178,9 @@ class FilteredTransaction internal constructor(
} }
// If at least one command is visible, then all command-signers should be visible as well. // 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. // This is required for visibility purposes, see FilteredTransaction.checkAllCommandsVisible() for more details.
if (componentGroupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal && !signersIncluded) { if (componentGroupIndex == COMMANDS_GROUP.ordinal && !signersIncluded) {
signersIncluded = true signersIncluded = true
val signersGroupIndex = ComponentGroupEnum.SIGNERS_GROUP.ordinal val signersGroupIndex = SIGNERS_GROUP.ordinal
// There exist commands, thus the signers group is not empty. // There exist commands, thus the signers group is not empty.
val signersGroupComponents = wtx.componentGroups.first { it.groupIndex == signersGroupIndex } val signersGroupComponents = wtx.componentGroups.first { it.groupIndex == signersGroupIndex }
filteredSerialisedComponents[signersGroupIndex] = signersGroupComponents.components.toMutableList() filteredSerialisedComponents[signersGroupIndex] = signersGroupComponents.components.toMutableList()
@ -176,16 +188,15 @@ class FilteredTransaction internal constructor(
filteredComponentHashes[signersGroupIndex] = wtx.availableComponentHashes[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" }
} }

View File

@ -33,7 +33,8 @@ 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.
@ -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 {

View File

@ -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)
} }

View File

@ -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());
} }
} }

View File

@ -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(

View File

@ -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)
} }

View File

@ -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
}
}

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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}")

View File

@ -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

View 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
}

View File

@ -33,8 +33,9 @@ class WhitelistGenerateCommand : CommandBase() {
override fun validateArguments() = paths.isNotEmpty() override fun validateArguments() = paths.isNotEmpty()
override fun handleCommand(): Boolean { override fun handleCommand(): Boolean {
val entries = AnalysisConfiguration().use { configuration ->
val entries = mutableListOf<String>() val entries = mutableListOf<String>()
val visitor = object : ClassAndMemberVisitor() { val visitor = object : ClassAndMemberVisitor(configuration, null) {
override fun visitClass(clazz: ClassRepresentation): ClassRepresentation { override fun visitClass(clazz: ClassRepresentation): ClassRepresentation {
entries.add(clazz.name) entries.add(clazz.name)
return super.visitClass(clazz) return super.visitClass(clazz)
@ -54,31 +55,31 @@ class WhitelistGenerateCommand : CommandBase() {
entries.add("${clazz.name}.${member.memberName}:${member.signature}") entries.add("${clazz.name}.${member.memberName}:${member.signature}")
} }
} }
val context = AnalysisContext.fromConfiguration(AnalysisConfiguration(), emptyList()) val context = AnalysisContext.fromConfiguration(configuration)
for (path in paths) { for (path in paths) {
ClassSource.fromPath(path).getStreamIterator().forEach { ClassSource.fromPath(path).getStreamIterator().forEach {
visitor.analyze(it, context) visitor.analyze(it, context)
} }
} }
val output = output entries
if (output != null) { }
Files.newOutputStream(output, StandardOpenOption.CREATE).use { output?.also {
GZIPOutputStream(it).use { Files.newOutputStream(it, StandardOpenOption.CREATE).use { out ->
PrintStream(it).use { GZIPOutputStream(out).use { gzip ->
it.println(""" PrintStream(gzip).use { pout ->
pout.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
} }

View File

@ -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.

View File

@ -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
} }
} }

View File

@ -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("/", ".")
} }
} }

View File

@ -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) {
/** /**

View File

@ -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
} }
/** /**

View File

@ -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()
) )
/** /**

View File

@ -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) {

View File

@ -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

View File

@ -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
} }
/** /**

View 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('.', '/')

View File

@ -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)
} }
} }

View File

@ -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)
} }

View File

@ -1,6 +0,0 @@
package net.corda.djvm.execution
/**
* Functionality runnable by a sandbox executor, marked for discoverability.
*/
interface DiscoverableRunnable

View File

@ -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.

View File

@ -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.
*/ */

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -1,19 +0,0 @@
package net.corda.djvm.execution
/**
* Functionality runnable by a sandbox executor.
*/
interface SandboxedRunnable<in TInput, out TOutput> : DiscoverableRunnable {
/**
* The entry point of the sandboxed functionality to be run.
*
* @param input The input to pass in to the entry point.
*
* @returns The output to pass back to the caller after the sandboxed code has finished running.
* @throws Exception The function can throw an exception, in which case the exception gets passed to the caller.
*/
@Throws(Exception::class)
fun run(input: TInput): TOutput?
}

View File

@ -53,7 +53,7 @@ class MemberFormatter(
* Check whether or not a signature is for a method. * Check whether or not a signature is for a method.
*/ */
fun isMethod(abbreviatedSignature: String): Boolean { fun isMethod(abbreviatedSignature: String): Boolean {
return abbreviatedSignature.startsWith("(") return abbreviatedSignature.startsWith('(')
} }
/** /**

View File

@ -82,8 +82,8 @@ class ClassHierarchy(
return findAncestors(get(className)).plus(get(OBJECT_NAME)) return findAncestors(get(className)).plus(get(OBJECT_NAME))
.asSequence() .asSequence()
.filterNotNull() .filterNotNull()
.map { memberModule.getFromClass(it, memberName, signature) } .mapNotNull { memberModule.getFromClass(it, memberName, signature) }
.firstOrNull { it != null } .firstOrNull()
.apply { .apply {
logger.trace("Getting rooted member for {}.{}:{} yields {}", className, memberName, signature, this) logger.trace("Getting rooted member for {}.{}:{} yields {}", className, memberName, signature, this)
} }

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