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()
@NotNull
public abstract java.time.Instant currentNodeTime()
public abstract int getProtocolVersion()
@NotNull
public abstract Iterable<String> getVaultTransactionNotes(net.corda.core.crypto.SecureHash)
@RPCReturnsObservables
@ -4449,8 +4448,6 @@ public interface net.corda.core.serialization.SerializationCustomSerializer
public abstract PROXY toProxy(OBJ)
##
public final class net.corda.core.serialization.SerializationDefaults extends java.lang.Object
@NotNull
public final net.corda.core.serialization.SerializationContext getCHECKPOINT_CONTEXT()
@NotNull
public final net.corda.core.serialization.SerializationContext getP2P_CONTEXT()
@NotNull
@ -6887,8 +6884,6 @@ public final class net.corda.testing.core.SerializationEnvironmentRule extends j
@NotNull
public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement, org.junit.runner.Description)
@NotNull
public final net.corda.core.serialization.SerializationContext getCheckpointContext()
@NotNull
public final net.corda.core.serialization.SerializationFactory getSerializationFactory()
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"

2
.idea/compiler.xml generated
View File

@ -235,4 +235,4 @@
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" />
</component>
</project>
</project>

View File

@ -84,6 +84,7 @@ see changes to this list.
* Giulio Katis (Westpac)
* Giuseppe Cardone (Intesa Sanpaolo)
* Guy Hochstetler (R3)
* Hristo Gatsinski (Industria)
* Ian Cusden (UBS)
* Ian Grigg (R3)
* Igor Nitto (R3)
@ -91,6 +92,7 @@ see changes to this list.
* Ivan Schasny (R3)
* James Brown (R3)
* James Carlyle (R3)
* Janis Olekss (Accenture)
* Jared Harwayne-Gidansky (BNY Mellon)
* Jayavaradhan Sambedu (Société Générale)
* Joel Dudley (R3)
@ -116,7 +118,7 @@ see changes to this list.
* Lars Stage Thomsen (Danske Bank)
* Lee Braine (Barclays)
* Lucas Salmen (Itau)
* Lulu Ren (S-Labs)
* Lulu Ren (Monad-Labs)
* Maksymilian Pawlak (R3)
* Marek Scocovsky (ABSA)
* marekdapps
@ -137,6 +139,7 @@ see changes to this list.
* Mike Hearn (R3)
* Mike Ward (R3)
* Mike Reichelt (US Bank)
* Milen Dobrinov (Industria)
* Mohamed Amine LEGHERABA
* Mustafa Ozturk (Natixis)
* Nick Skinner (Northern Trust)
@ -145,6 +148,7 @@ see changes to this list.
* Nuam Athaweth (MUFG)
* Oscar Zibordi de Paiva (Scopus Soluções em TI)
* OP Financial
* Parnika Sharma (BCS Technology)
* Patrick Kuo (R3)
* Pekka Kaipio (OP Financial)
* Phillip Griffin
@ -176,6 +180,7 @@ see changes to this list.
* Scott James
* Sean Zhang (Wells Fargo)
* Shams Asari (R3)
* Shivan Sawant (Persistent Systems Limited)
* Siddhartha Sengupta (Tradewind Markets)
* Simon Taylor (Barclays)
* Sofus Mortensen (Digital Asset Holdings)

View File

@ -17,9 +17,9 @@ buildscript {
ext.quasar_group = 'co.paralleluniverse'
ext.quasar_version = '0.7.10'
// gradle-capsule-plugin:1.0.2 contains capsule:1.0.1
// TODO: Upgrade gradle-capsule-plugin to a version with capsule:1.0.3
ext.capsule_version = '1.0.1'
// gradle-capsule-plugin:1.0.2 contains capsule:1.0.1 by default.
// We must configure it manually to use the latest capsule version.
ext.capsule_version = '1.0.3'
ext.asm_version = '5.0.4'
ext.artemis_version = '2.6.2'
@ -32,6 +32,7 @@ buildscript {
ext.bouncycastle_version = constants.getProperty("bouncycastleVersion")
ext.guava_version = constants.getProperty("guavaVersion")
ext.caffeine_version = constants.getProperty("caffeineVersion")
ext.disruptor_version = constants.getProperty("disruptorVersion")
ext.metrics_version = constants.getProperty("metricsVersion")
ext.metrics_new_relic_version = constants.getProperty("metricsNewRelicVersion")
ext.okhttp_version = '3.5.0'
@ -46,7 +47,7 @@ buildscript {
ext.hibernate_version = '5.3.6.Final'
ext.h2_version = '1.4.197' // Update docs if renamed or removed.
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.eddsa_version = '0.2.0'
ext.dependency_checker_version = '3.1.0'
@ -55,9 +56,8 @@ buildscript {
ext.crash_version = 'cadb53544fbb3c0fb901445da614998a6a419488'
ext.jsr305_version = constants.getProperty("jsr305Version")
ext.shiro_version = '1.4.0'
ext.shadow_version = '2.0.4'
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.snake_yaml_version = constants.getProperty('snakeYamlVersion')
ext.docker_compose_rule_version = '0.33.0'
@ -78,6 +78,7 @@ buildscript {
// Update 121 is required for ObjectInputFilter.
// 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'
repositories {
@ -115,8 +116,12 @@ buildscript {
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,
// 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
id "us.kirchmeier.capsule" version "1.0.2"
// Version 1.0.2 of this plugin uses capsule:1.0.1 by default.
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 {
@ -195,11 +200,18 @@ allprojects {
if (System.getProperty("test.maxParallelForks") != null) {
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) {
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'
@ -209,6 +221,7 @@ allprojects {
mavenLocal()
mavenCentral()
jcenter()
maven { url "$artifactory_contextUrl/corda-dependencies" }
maven { url 'https://jitpack.io' }
}
@ -235,6 +248,8 @@ allprojects {
// We want to use SLF4J's version of these bindings: jcl-over-slf4j
// Remove any transitive dependency on Apache's version.
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.
// Exclude it to force us to use the individual Netty modules instead.
@ -255,12 +270,6 @@ allprojects {
if (!JavaVersion.current().java8Compatible)
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.
dependencies {
compile project(':node')
@ -333,6 +342,8 @@ bintrayConfig {
'corda-rpc',
'corda-core',
'corda-core-deterministic',
'corda-deterministic-verifier',
'corda-djvm',
'corda',
'corda-finance',
'corda-node',
@ -350,7 +361,8 @@ bintrayConfig {
'corda-serialization-deterministic',
'corda-tools-blob-inspector',
'corda-tools-explorer',
'corda-tools-network-bootstrapper'
'corda-tools-network-bootstrapper',
'corda-tools-cliutils'
]
license {
name = 'Apache-2.0'
@ -382,7 +394,7 @@ artifactory {
contextUrl = artifactory_contextUrl
repository {
repoKey = 'corda-dev'
username = 'teamcity'
username = System.getenv('CORDA_ARTIFACTORY_USERNAME')
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD')
}
@ -431,6 +443,11 @@ if (file('corda-docs-only-build').exists() || (System.getenv('CORDA_DOCS_ONLY_BU
}
wrapper {
gradleVersion = "4.8.1"
gradleVersion = "4.10.1"
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'
include 'canonicalizer'
apply from: '../buildCacheSettings.gradle'

View File

@ -197,7 +197,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
fun DigitalSignatureWithCert() {
val digitalSignature = DigitalSignatureWithCert(MINI_CORP.identity.certificate, secureRandomBytes(128))
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(bytes.binaryValue()).isEqualTo(digitalSignature.bytes)
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["issuer"].valueAs<X500Principal>(mapper)).isEqualTo(cert.issuerX500Principal)
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["notBefore"].valueAs<Date>(mapper)).isEqualTo(cert.notBefore)
assertThat(json["encoded"].binaryValue()).isEqualTo(cert.encoded)

View File

@ -1,5 +1,6 @@
package net.corda.client.jfx.model
import com.github.benmanes.caffeine.cache.CacheLoader
import com.github.benmanes.caffeine.cache.Caffeine
import javafx.beans.value.ObservableValue
import javafx.collections.FXCollections
@ -31,7 +32,7 @@ class NetworkIdentityModel {
private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable)
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)) } }
})
val notaries = ChosenList(rpcProxy.map { FXCollections.observableList(it?.cordaRPCOps?.notaryIdentities() ?: emptyList()) }, "notaries")

View File

@ -55,7 +55,7 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
@Before
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()));
}

View File

@ -47,7 +47,7 @@ class RPCStabilityTests {
}
object DummyOps : RPCOps {
override val protocolVersion = 0
override val protocolVersion = 1000
}
private fun waitUntilNumberOfThreadsStable(executorService: ScheduledExecutorService): Map<Thread, List<StackTraceElement>> {
@ -107,7 +107,7 @@ class RPCStabilityTests {
Try.on {
startRpcClient<RPCOps>(
server.get().broker.hostAndPort!!,
configuration = CordaRPCClientConfiguration.DEFAULT.copy(minimumServerProtocolVersion = 1)
configuration = CordaRPCClientConfiguration.DEFAULT.copy(minimumServerProtocolVersion = 1000)
).get()
}
}
@ -203,7 +203,7 @@ class RPCStabilityTests {
rpcDriver {
val leakObservableOpsImpl = object : LeakObservableOps {
val leakedUnsubscribedCount = AtomicInteger(0)
override val protocolVersion = 0
override val protocolVersion = 1000
override fun leakObservable(): Observable<Nothing> {
return PublishSubject.create<Nothing>().doOnUnsubscribe {
leakedUnsubscribedCount.incrementAndGet()
@ -234,7 +234,7 @@ class RPCStabilityTests {
fun `client reconnects to rebooted server`() {
rpcDriver {
val ops = object : ReconnectOps {
override val protocolVersion = 0
override val protocolVersion = 1000
override fun ping() = "pong"
}
@ -259,7 +259,7 @@ class RPCStabilityTests {
fun `connection failover fails, rpc calls throw`() {
rpcDriver {
val ops = object : ReconnectOps {
override val protocolVersion = 0
override val protocolVersion = 1000
override fun ping() = "pong"
}
@ -290,7 +290,7 @@ class RPCStabilityTests {
fun `observables error when connection breaks`() {
rpcDriver {
val ops = object : NoOps {
override val protocolVersion = 0
override val protocolVersion = 1000
override fun subscribe(): Observable<Nothing> {
return PublishSubject.create<Nothing>()
}
@ -350,7 +350,7 @@ class RPCStabilityTests {
fun `client connects to first available server`() {
rpcDriver {
val ops = object : ServerOps {
override val protocolVersion = 0
override val protocolVersion = 1000
override fun serverId() = "server"
}
val serverFollower = shutdownManager.follower()
@ -371,15 +371,15 @@ class RPCStabilityTests {
fun `3 server failover`() {
rpcDriver {
val ops1 = object : ServerOps {
override val protocolVersion = 0
override val protocolVersion = 1000
override fun serverId() = "server1"
}
val ops2 = object : ServerOps {
override val protocolVersion = 0
override val protocolVersion = 1000
override fun serverId() = "server2"
}
val ops3 = object : ServerOps {
override val protocolVersion = 0
override val protocolVersion = 1000
override fun serverId() = "server3"
}
val serverFollower1 = shutdownManager.follower()
@ -443,7 +443,7 @@ class RPCStabilityTests {
fun `server cleans up queues after disconnected clients`() {
rpcDriver {
val trackSubscriberOpsImpl = object : TrackSubscriberOps {
override val protocolVersion = 0
override val protocolVersion = 1000
val subscriberCount = AtomicInteger(0)
val trackSubscriberCountObservable = UnicastSubject.create<Unit>().share().
doOnSubscribe { subscriberCount.incrementAndGet() }.
@ -486,7 +486,7 @@ class RPCStabilityTests {
}
class SlowConsumerRPCOpsImpl : SlowConsumerRPCOps {
override val protocolVersion = 0
override val protocolVersion = 1000
override fun streamAtInterval(interval: Duration, size: Int): Observable<ByteArray> {
val chunk = ByteArray(size)
@ -587,7 +587,7 @@ class RPCStabilityTests {
val request = RPCApi.ClientToServer.fromClientMessage(it)
when (request) {
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)
reply.writeToClientMessage(SerializationDefaults.RPC_SERVER_CONTEXT, message)
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.core.context.Actor
import net.corda.core.context.Trace
import net.corda.core.identity.CordaX500Name
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.serialization.internal.effectiveSerializationEnv
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcConnectorTcpTransport
import net.corda.core.messaging.ClientRpcSslOptions
import net.corda.core.utilities.days
import net.corda.core.utilities.minutes
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 java.time.Duration
@ -29,65 +30,76 @@ class CordaRPCConnection internal constructor(connection: RPCConnection<CordaRPC
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,
/**
* 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
* returned Observable stream the stack trace of the originating RPC will be shown as well. Note that
* constructing call stacks is a moderately expensive operation.
* If set to true the client will track RPC call sites (default is false). If an error occurs subsequently
* during the RPC or in a returned Observable stream the stack trace of the originating RPC will be shown as
* 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
* 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,
/**
* 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,
/**
* Determines the concurrency level of the Observable Cache. This is exposed because it implicitly determines
* the limit on the number of leaked observables reaped because of garbage collection per reaping.
* See the implementation of [com.google.common.cache.LocalCache] for details.
* This property is no longer used and has no effect.
* @suppress
*/
@Deprecated("This field is no longer used and has no effect.")
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,
/**
* 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,
/**
* Maximum reconnect attempts on failover>
* Maximum reconnect attempts on failover or disconnection. The default is -1 which means unlimited.
*/
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,
// 10 MiB maximum allowed file size for attachments, including message headers.
// 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
@ -97,6 +109,7 @@ open class CordaRPCClientConfiguration @JvmOverloads constructor(
private const val unlimitedReconnectAttempts = -1
/** Provides an instance of this class with the parameters set to our recommended defaults. */
@JvmField
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.
*
* @suppress
*/
@Suppress("DEPRECATION")
@JvmOverloads
fun copy(
connectionMaxRetryInterval: Duration = this.connectionMaxRetryInterval,
@ -169,6 +185,7 @@ open class CordaRPCClientConfiguration @JvmOverloads constructor(
return result
}
@Suppress("DEPRECATION")
override fun toString(): String {
return "CordaRPCClientConfiguration(" +
"connectionMaxRetryInterval=$connectionMaxRetryInterval, " +
@ -180,7 +197,8 @@ open class CordaRPCClientConfiguration @JvmOverloads constructor(
"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
}
@ -226,10 +244,8 @@ class CordaRPCClient private constructor(
private val hostAndPort: NetworkHostAndPort,
private val configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
private val sslConfiguration: ClientRpcSslOptions? = null,
private val nodeSslConfiguration: SSLConfiguration? = null,
private val classLoader: ClassLoader? = null,
private val haAddressPool: List<NetworkHostAndPort> = emptyList(),
private val internalConnection: Boolean = false
private val haAddressPool: List<NetworkHostAndPort> = emptyList()
) {
@JvmOverloads
constructor(hostAndPort: NetworkHostAndPort,
@ -243,7 +259,7 @@ class CordaRPCClient private constructor(
* @param configuration An optional configuration used to tweak client behaviour.
*/
@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 {
fun createWithSsl(
@ -268,16 +284,7 @@ class CordaRPCClient private constructor(
sslConfiguration: ClientRpcSslOptions? = null,
classLoader: ClassLoader? = null
): CordaRPCClient {
return CordaRPCClient(hostAndPort, configuration, sslConfiguration, null, 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)
return CordaRPCClient(hostAndPort, configuration, sslConfiguration, classLoader)
}
}
@ -295,9 +302,6 @@ class CordaRPCClient private constructor(
private fun getRpcClient(): RPCClient<CordaRPCOps> {
return when {
// Node->RPC broker, mutually authenticated SSL. This is used when connecting the integrated shell
internalConnection == true -> RPCClient(hostAndPort, nodeSslConfiguration!!)
// Client->RPC broker
haAddressPool.isEmpty() -> RPCClient(
rpcConnectorTcpTransport(hostAndPort, config = sslConfiguration),
@ -326,6 +330,21 @@ class CordaRPCClient private constructor(
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]
* 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 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.
* @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 {
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
/**
* Thrown to indicate a fatal error in the RPC system itself, as opposed to an error generated by the invoked
* method.
* Thrown to indicate a fatal error in the RPC system itself, as opposed to an error generated by the invoked method.
*/
open class RPCException(message: String?, cause: Throwable?) : CordaRuntimeException(message, cause) {
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.CordaRPCClientConfiguration
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.pendingFlowsCount
import net.corda.core.utilities.NetworkHostAndPort
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 */
fun createCordaRPCClientWithSslAndClassLoader(
@ -15,21 +11,4 @@ fun createCordaRPCClientWithSslAndClassLoader(
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
sslConfiguration: ClientRpcSslOptions? = null,
classLoader: ClassLoader? = null
) = 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 { }
}
) = CordaRPCClient.createWithSslAndClassLoader(hostAndPort, configuration, sslConfiguration, classLoader)

View File

@ -6,6 +6,7 @@ import net.corda.client.rpc.RPCException
import net.corda.core.context.Actor
import net.corda.core.context.Trace
import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.logElapsedTime
import net.corda.core.internal.uncheckedCast
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.utilities.NetworkHostAndPort
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.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.TransportConfiguration
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
@ -43,7 +44,7 @@ class RPCClient<I : RPCOps>(
constructor(
hostAndPort: NetworkHostAndPort,
sslConfiguration: SSLConfiguration,
sslConfiguration: SslConfiguration,
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
serializationContext: SerializationContext = SerializationDefaults.RPC_CLIENT_CONTEXT
) : this(rpcInternalClientTcpTransport(hostAndPort, sslConfiguration), configuration, serializationContext)
@ -65,7 +66,8 @@ class RPCClient<I : RPCOps>(
username: String,
password: String,
externalTrace: Trace? = null,
impersonatedActor: Actor? = null
impersonatedActor: Actor? = null,
targetLegalIdentity: CordaX500Name? = null
): RPCConnection<I> {
return log.logElapsedTime("Startup") {
val clientAddress = SimpleString("${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$username.${random63BitValue()}")
@ -85,7 +87,8 @@ class RPCClient<I : RPCOps>(
isUseGlobalPools = nodeSerializationEnv != null
}
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 {
proxyHandler.start()
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.google.common.util.concurrent.SettableFuture
import com.google.common.util.concurrent.ThreadFactoryBuilder
import net.corda.client.rpc.ConnectionFailureException
import net.corda.client.rpc.CordaRPCClientConfiguration
import net.corda.client.rpc.RPCException
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.Trace
import net.corda.core.context.Trace.InvocationId
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.*
import net.corda.core.messaging.RPCOps
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.
* 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
* 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
@ -80,7 +82,8 @@ class RPCClientProxyHandler(
serializationContext: SerializationContext,
private val sessionId: Trace.SessionId,
private val externalTrace: Trace?,
private val impersonatedActor: Actor?
private val impersonatedActor: Actor?,
private val targetLegalIdentity: CordaX500Name?
) : InvocationHandler {
private enum class State {
@ -97,12 +100,18 @@ class RPCClientProxyHandler(
// To check whether toString() is being invoked
val toStringMethod: Method = Object::toString.javaMethod!!
private fun addRpcCallSiteToThrowable(throwable: Throwable, callSite: Throwable) {
private fun addRpcCallSiteToThrowable(throwable: Throwable, callSite: CallSite) {
var currentThrowable = throwable
while (true) {
val cause = currentThrowable.cause
if (cause == null) {
currentThrowable.initCause(callSite)
try {
currentThrowable.initCause(callSite)
} catch (e: IllegalStateException) {
// OK, we did our best, but the first throwable with a null cause was instantiated using
// Throwable(Throwable) or Throwable(String, Throwable) which means initCause can't ever
// be called even if it was passed null.
}
break
} else {
currentThrowable = cause
@ -146,15 +155,17 @@ class RPCClientProxyHandler(
private fun createRpcObservableMap(): RpcObservableMap {
val onObservableRemove = RemovalListener<InvocationId, UnicastSubject<Notification<*>>> { key, _, cause ->
val observableId = key!!
val rpcCallSite = callSiteMap?.remove(observableId)
val rpcCallSite: CallSite? = callSiteMap?.remove(observableId)
if (cause == RemovalCause.COLLECTED) {
log.warn(listOf(
"A hot observable returned from an RPC was never subscribed to.",
"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",
"to close it explicitly. (Java users: subscribe to it then unsubscribe). This warning",
"will appear less frequently in future versions of the platform and you can ignore it",
"if you want to.").joinToString(" "), rpcCallSite)
"to close it explicitly. (Java users: subscribe to it then unsubscribe). If you aren't sure",
"where the leak is coming from, set -Dnet.corda.client.rpc.trackRpcCallSites=true on the JVM",
"command line and you will get a stack trace with this warning."
).joinToString(" "), rpcCallSite)
rpcCallSite?.printStackTrace()
}
observablesToReap.locked { observables.add(observableId) }
}
@ -215,6 +226,9 @@ class RPCClientProxyHandler(
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.
override fun invoke(proxy: Any, method: Method, arguments: Array<out Any?>?): Any? {
lifeCycle.requireState { it == State.STARTED || it == State.SERVER_VERSION_NOT_SET }
@ -230,7 +244,7 @@ class RPCClientProxyHandler(
throw RPCException("RPC server is not available.")
val replyId = InvocationId.newInstance()
callSiteMap?.set(replyId, Throwable("<Call site of root RPC '${method.name}'>"))
callSiteMap?.set(replyId, CallSite(method.name))
try {
val serialisedArguments = (arguments?.toList() ?: emptyList()).serialize(context = serializationContextWithObservableContext)
val request = RPCApi.ClientToServer.RpcRequest(
@ -263,6 +277,9 @@ class RPCClientProxyHandler(
private fun sendMessage(message: RPCApi.ClientToServer) {
val artemisMessage = producerSession!!.createMessage(false)
message.writeToClientMessage(artemisMessage)
targetLegalIdentity?.let {
artemisMessage.putStringProperty(RPCApi.RPC_TARGET_LEGAL_IDENTITY, it.toString())
}
sendExecutor!!.submit {
artemisMessage.putLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME, deduplicationSequenceNumber.getAndIncrement())
log.debug { "-> RPC -> $message" }
@ -273,7 +290,7 @@ class RPCClientProxyHandler(
// The handler for Artemis messages.
private fun artemisMessageHandler(message: ClientMessage) {
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)
future?.setException(e.cause ?: e)
}
@ -536,7 +553,7 @@ class RPCClientProxyHandler(
m.keys.forEach { k ->
observationExecutorPool.run(k) {
try {
m[k]?.onError(RPCException("Connection failure detected."))
m[k]?.onError(ConnectionFailureException())
} catch (th: Throwable) {
log.error("Unexpected exception when RPC connection failure handling", th)
}
@ -545,7 +562,7 @@ class RPCClientProxyHandler(
observableContext.observableMap.invalidateAll()
rpcReplyMap.forEach { _, replyFuture ->
replyFuture.setException(RPCException("Connection failure detected."))
replyFuture.setException(ConnectionFailureException())
}
rpcReplyMap.clear()
@ -555,13 +572,14 @@ class RPCClientProxyHandler(
private typealias RpcObservableMap = Cache<InvocationId, UnicastSubject<Notification<*>>>
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.
*
* @param 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 observableMap holds the Observables that are ultimately exposed to the user.
* @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(
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.RPCClientProxyHandler
import net.corda.core.context.Trace
import net.corda.core.serialization.SerializationContext
import net.corda.core.utilities.loggerFor
import net.corda.nodeapi.RPCApi
import net.corda.serialization.internal.amqp.*
import org.apache.qpid.proton.codec.Data
@ -17,11 +19,12 @@ import java.util.concurrent.atomic.AtomicInteger
import javax.transaction.NotSupportedException
/**
* 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,
* what is actually sent is a reference to the observable that can then be subscribed to.
* 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 class [RpcServerObservableSerializer] can only serialize them. Observables
* 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) {
private val log = loggerFor<RpcClientObservableDeSerializer>()
private object RpcObservableContextKey
fun createContext(
@ -96,22 +99,23 @@ object RpcClientObservableDeSerializer : CustomSerializer.Implements<Observable<
}
val rpcCallSite = getRpcCallSite(context, observableContext)
observableContext.observableMap.put(observableId, observable)
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
// don't need to store a reference to the Observables themselves.
return pinInSubscriptions(observable, observableContext.hardReferenceStore).doOnUnsubscribe {
// 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.
// The unsubscribe is due to [ObservableToFuture]'s use of first().
// The unsubscribe is due to ObservableToFuture's use of first().
observableContext.observableMap.invalidate(observableId)
}.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
// Will only return non-null if the trackRpcCallSites option in the RPC configuration has been specified.
return observableContext.callSiteMap?.get(rpcRequestOrObservableId)
}

View File

@ -48,7 +48,7 @@ class ClientRPCInfrastructureTests : AbstractRPCTest() {
fun makeComplicatedListenableFuture(): CordaFuture<Pair<String, CordaFuture<String>>>
@RPCSinceVersion(2)
@RPCSinceVersion(2000)
fun addedLater()
fun captureUser(): String
@ -58,7 +58,7 @@ class ClientRPCInfrastructureTests : AbstractRPCTest() {
private lateinit var complicatedListenableFuturee: CordaFuture<Pair<String, CordaFuture<String>>>
inner class TestOpsImpl : TestOps {
override val protocolVersion = 1
override val protocolVersion = 1000
// do not remove Unit
override fun barf(): Unit = throw IllegalArgumentException("Barf!")
override fun void() {}

View File

@ -33,7 +33,7 @@ class RPCConcurrencyTests : AbstractRPCTest() {
@CordaSerializable
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 waitLatch(id: Long)
fun downLatch(id: Long)
@ -43,7 +43,7 @@ class RPCConcurrencyTests : AbstractRPCTest() {
class TestOpsImpl(private val pool: Executor) : TestOps {
private val latches = ConcurrentHashMap<Long, CountDownLatch>()
override val protocolVersion = 0
override val protocolVersion = 1000
override fun newLatch(numberOfDowns: Int): Long {
val id = random63BitValue()

View File

@ -26,7 +26,7 @@ class RPCFailureTests {
}
class OpsImpl : Ops {
override val protocolVersion = 1
override val protocolVersion = 1000
override fun getUnserializable() = Unserializable()
override fun getUnserializableAsync(): CordaFuture<Unserializable> {
return openFuture<Unserializable>().apply { capture { getUnserializable() } }

View File

@ -24,7 +24,7 @@ class RPCHighThroughputObservableTests : AbstractRPCTest() {
}
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 }
}

View File

@ -5,8 +5,8 @@ import net.corda.core.messaging.RPCOps
import net.corda.core.utilities.minutes
import net.corda.core.utilities.seconds
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.node.internal.RPCDriverDSL
import net.corda.testing.node.internal.performance.startPublishingFixedRateInjector
import net.corda.testing.node.internal.performance.startReporter
import net.corda.testing.node.internal.performance.startTightLoopInjector
@ -34,7 +34,7 @@ class RPCPerformanceTests : AbstractRPCTest() {
}
class TestOpsImpl : TestOps {
override val protocolVersion = 0
override val protocolVersion = 1000
override fun simpleReply(input: ByteArray, sizeOfReply: Int): ByteArray {
return ByteArray(sizeOfReply)
}

View File

@ -25,7 +25,7 @@ class RPCPermissionsTests : AbstractRPCTest() {
}
class TestOpsImpl : TestOps {
override val protocolVersion = 1
override val protocolVersion = 1000
override fun validatePermission(method: String, target: String?) {
val authorized = if (target == null) {
rpcContext().isPermitted(method)

View File

@ -46,7 +46,7 @@
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="10MB"/>
<SizeBasedTriggeringPolicy size="100MB"/>
</Policies>
<DefaultRolloverStrategy min="1" max="100">

View File

@ -1,9 +1,12 @@
gradlePluginsVersion=4.0.29
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
guavaVersion=25.1-jre
proguardVersion=6.0.3
bouncycastleVersion=1.57
bouncycastleVersion=1.60
disruptorVersion=3.4.2
typesafeConfigVersion=1.3.1
jsr305Version=3.0.2
artifactoryPluginVersion=4.7.3

View File

@ -11,8 +11,8 @@ def javaHome = System.getProperty('java.home')
def jarBaseName = "corda-${project.name}".toString()
configurations {
runtimeLibraries
runtimeArtifacts.extendsFrom runtimeLibraries
deterministicLibraries
deterministicArtifacts.extendsFrom deterministicLibraries
}
dependencies {
@ -20,14 +20,14 @@ 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.
runtimeLibraries "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
runtimeLibraries "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
runtimeLibraries "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final"
runtimeLibraries "org.bouncycastle:bcprov-jdk15on:$bouncycastle_version"
runtimeLibraries "org.bouncycastle:bcpkix-jdk15on:$bouncycastle_version"
runtimeLibraries "com.google.code.findbugs:jsr305:$jsr305_version"
runtimeLibraries "net.i2p.crypto:eddsa:$eddsa_version"
runtimeLibraries "org.slf4j:slf4j-api:$slf4j_version"
deterministicLibraries "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
deterministicLibraries "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
deterministicLibraries "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final"
deterministicLibraries "org.bouncycastle:bcprov-jdk15on:$bouncycastle_version"
deterministicLibraries "org.bouncycastle:bcpkix-jdk15on:$bouncycastle_version"
deterministicLibraries "com.google.code.findbugs:jsr305:$jsr305_version"
deterministicLibraries "net.i2p.crypto:eddsa:$eddsa_version"
deterministicLibraries "org.slf4j:slf4j-api:$slf4j_version"
}
jar {
@ -50,6 +50,7 @@ task patchCore(type: Zip, dependsOn: coreJarTask) {
from(zipTree(originalJar)) {
exclude 'net/corda/core/internal/*ToggleField*.class'
exclude 'net/corda/core/serialization/*SerializationFactory*.class'
exclude 'net/corda/core/serialization/internal/CheckpointSerializationFactory*.class'
}
reproducibleFileOrder = true
@ -112,7 +113,7 @@ task determinise(type: ProGuardTask) {
libraryjars file("$javaHome/lib/rt.jar")
libraryjars file("$javaHome/lib/jce.jar")
configurations.runtimeLibraries.forEach {
configurations.deterministicLibraries.forEach {
libraryjars it, filter: '!META-INF/versions/**'
}
@ -152,7 +153,7 @@ task checkDeterminism(type: ProGuardTask, dependsOn: jdkTask) {
libraryjars deterministic_rt_jar
configurations.runtimeLibraries.forEach {
configurations.deterministicLibraries.forEach {
libraryjars it, filter: '!META-INF/versions/**'
}
@ -173,12 +174,12 @@ assemble.dependsOn checkDeterminism
def deterministicJar = metafix.outputs.files.singleFile
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 {
dependenciesFrom configurations.runtimeArtifacts
dependenciesFrom configurations.deterministicArtifacts
publishSources = false
publishJavadoc = false
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'
dependencies {
testCompile project(path: ':core-deterministic', configuration: 'runtimeArtifacts')
testCompile project(path: ':serialization-deterministic', configuration: 'runtimeArtifacts')
testCompile project(path: ':core-deterministic', configuration: 'deterministicArtifacts')
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(':core-deterministic:testing:common')
testCompile(project(':finance')) {
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(':finance')
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-reflect"

View File

@ -1,8 +1,8 @@
package net.corda.deterministic.data
import net.corda.core.serialization.deserialize
import net.corda.deterministic.common.LocalSerializationRule
import net.corda.deterministic.common.TransactionVerificationRequest
import net.corda.deterministic.verifier.LocalSerializationRule
import net.corda.deterministic.verifier.TransactionVerificationRequest
import org.junit.Before
import org.junit.Rule
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.Party
import net.corda.core.serialization.serialize
import net.corda.deterministic.common.MockContractAttachment
import net.corda.deterministic.common.SampleCommandData
import net.corda.deterministic.common.TransactionVerificationRequest
import net.corda.deterministic.verifier.MockContractAttachment
import net.corda.deterministic.verifier.SampleCommandData
import net.corda.deterministic.verifier.TransactionVerificationRequest
import net.corda.finance.POUNDS
import net.corda.finance.`issued by`
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.deterministic.KeyStoreProvider
import net.corda.deterministic.CheatingSecurityProvider
import net.corda.deterministic.common.LocalSerializationRule
import net.corda.deterministic.verifier.LocalSerializationRule
import org.junit.*
import org.junit.rules.RuleChain
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.SerializationContext
@ -83,4 +83,4 @@ class LocalSerializationRule(private val label: String) : TestRule {
return canDeserializeVersion(magic) && target == P2P
}
}
}
}

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

View File

@ -1,5 +1,5 @@
@file:JvmName("SampleData")
package net.corda.deterministic.common
package net.corda.deterministic.verifier
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.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 cordappClasses: List<String>
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. */
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)
/** 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]. */
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 {
return if (this is CompositeKey) keys.intersect(otherKeys).isNotEmpty()
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.pkcs.PrivateKeyInfo
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.jcajce.provider.asymmetric.ec.AlgorithmParametersSpi
import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider
import java.security.SecureRandom
import java.security.Security
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 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 {
// 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.
@ -46,4 +50,4 @@ internal val bouncyCastlePQCProvider = BouncyCastlePQCProvider().apply {
internal val providerMap = listOf(cordaBouncyCastleProvider, cordaSecurityProvider, bouncyCastlePQCProvider).map { it.name to it }.toMap()
@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
* 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 {
constructor(message: String?, cause: Throwable?) : this(message, cause, null)
constructor(message: String?) : this(message, null)
constructor(cause: Throwable?) : this(cause?.toString(), cause)
constructor() : this(null, null)
var originalErrorId: Long? = null
override fun getErrorId(): Long? = originalErrorId
}
// 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.
// 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 {
/** Intermediate CA (Doorman service). */
INTERMEDIATE_CA(NonEmptySet.of(null), false, false),
/** Signing certificate for the Doorman CA. */
DOORMAN_CA(NonEmptySet.of(null), false, false),
/** Signing certificate for the network map. */
NETWORK_MAP(NonEmptySet.of(null), false, false),
/** 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_CA(NonEmptySet.of(INTERMEDIATE_CA), false, false),
NODE_CA(NonEmptySet.of(DOORMAN_CA), false, false),
/** Transport layer security certificate for a node. */
TLS(NonEmptySet.of(NODE_CA), false, false),
/** 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
// own Root CA and Intermediate 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.
LEGAL_IDENTITY(NonEmptySet.of(INTERMEDIATE_CA, NODE_CA), true, true),
// own Root CA and Doorman CA directly issues Legal Identities; thus, there won't be a requirement for
// Node CA). Consider removing [DOORMAN_CA] from [validParents] when the model is finalised.
LEGAL_IDENTITY(NonEmptySet.of(DOORMAN_CA, NODE_CA), true, true),
/** Confidential (limited visibility) identity of a legal entity. */
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.verify
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
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
// and the correct exceptions will be need to be annotated
/** A digital signature with attached certificate of the public key. */
class DigitalSignatureWithCert(val by: X509Certificate, bytes: ByteArray) : DigitalSignature(bytes) {
/** 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, 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: 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. */

View File

@ -17,6 +17,7 @@ import net.corda.core.serialization.SerializeAsTokenContext
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NonEmptySet
import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.debug
import net.corda.core.utilities.unwrap
import java.nio.file.FileAlreadyExistsException
import java.util.*
@ -75,7 +76,7 @@ sealed class FetchDataFlow<T : NamedByHash, in W : Any>(
return if (toFetch.isEmpty()) {
Result(fromDisk, emptyList())
} 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.
// 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.
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)
Result(fromDisk, downloaded)
}

View File

@ -4,21 +4,11 @@ package net.corda.core.internal
import net.corda.core.DeleteForDJVM
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.flows.FlowLogic
import net.corda.core.node.ServicesForResolution
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.UntrustworthyData
import org.slf4j.Logger
import org.slf4j.MDC
import rx.Observable
import rx.Observer
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>()
}
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> {
checkCacheName(name)
return this.build<K, V>(loader)

View File

@ -24,25 +24,29 @@ data class CordappImpl(
override val customSchemas: Set<MappedSchema>,
override val allFlows: List<Class<out FlowLogic<*>>>,
override val jarPath: URL,
val info: Info,
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
*
* 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 {
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 {
return setOf(shortName, vendor, version).any { it == UNKNOWN_VALUE }
}
fun hasUnknownFields(): Boolean = arrayOf(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

@ -67,4 +67,4 @@ abstract class TrustedAuthorityNotaryService : NotaryService() {
}
// TODO: Sign multiple transactions at once by building their Merkle tree and then signing over its root.
}
}

View File

@ -22,7 +22,6 @@ import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.Try
import rx.Observable
import rx.subjects.PublishSubject
import java.io.IOException
import java.io.InputStream
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
* 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. */
val progressTrackerStepAndUpdates: DataFeed<String, String>?,
/** An [InvocationContext] describing why and by whom the flow was started. */
@ -76,7 +76,8 @@ sealed class StateMachineUpdate {
// DOCSTART 1
/**
* 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 parameters new [NetworkParameters] data structure
* @property description description of the update
@ -96,12 +97,6 @@ data class StateMachineTransactionMapping(val stateMachineRunId: StateMachineRun
/** RPC operations that the node exposes to clients. */
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. */
fun stateMachinesSnapshot(): List<StateMachineInfo>
@ -233,6 +228,9 @@ interface CordaRPCOps : RPCOps {
@RPCReturnsObservables
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)
* 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.
*/
fun shutdown()
}
/**
* Returns a [DataFeed] that keeps track on the count of pending flows.
*/
fun CordaRPCOps.pendingFlowsCount(): DataFeed<Int, Pair<Int, Int>> {
/**
* 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 terminate(drainPendingFlows: Boolean = false)
val stateMachineState = stateMachinesFeed()
var pendingFlowsCount = stateMachineState.snapshot.size
var completedFlowsCount = 0
val updates = PublishSubject.create<Pair<Int, Int>>()
stateMachineState
.updates
.doOnNext { update ->
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)
/**
* Returns whether the node is waiting for pending flows to complete before shutting down.
* Disabling draining mode cancels this state.
*
* @return whether the node will shutdown when the pending flows count reaches zero.
*/
fun isWaitingForShutdown(): Boolean
}
inline fun <reified T : ContractState> CordaRPCOps.vaultQueryBy(criteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(),

View File

@ -28,4 +28,4 @@ interface AppServiceHub : ServiceHub {
* TODO it is assumed here that the flow object has an appropriate classloader.
*/
fun <T> startTrackedFlow(flow: FlowLogic<T>): FlowProgressHandle<T>
}
}

View File

@ -140,4 +140,4 @@ interface IdentityService {
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 lockId: String?,
val lockUpdateTime: Instant?,
val isRelevant: Vault.RelevancyStatus?
val relevancyStatus: Vault.RelevancyStatus?
) {
constructor(ref: StateRef,
contractStateClassName: String,

View File

@ -73,7 +73,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
abstract class CommonQueryCriteria : QueryCriteria() {
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>>?
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
return parser.parseCriteria(this)
@ -90,7 +90,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
val notary: List<AbstractParty>? = null,
val softLockingCondition: SoftLockingCondition? = null,
val timeCondition: TimeCondition? = null,
override val isRelevant: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
override val relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
) : CommonQueryCriteria() {
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
super.visit(parser)
@ -125,15 +125,15 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
val externalId: List<String>? = null,
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
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() {
constructor(
participants: List<AbstractParty>? = null,
linearId: List<UniqueIdentifier>? = null,
status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
contractStateTypes: Set<Class<out ContractState>>? = null,
isRelevant: Vault.RelevancyStatus
) : this(participants, linearId?.map { it.id }, linearId?.mapNotNull { it.externalId }, status, contractStateTypes, isRelevant)
relevancyStatus: Vault.RelevancyStatus
) : this(participants, linearId?.map { it.id }, linearId?.mapNotNull { it.externalId }, status, contractStateTypes, relevancyStatus)
constructor(
participants: List<AbstractParty>? = null,
@ -175,7 +175,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
val issuerRef: List<OpaqueBytes>? = null,
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
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() {
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
super.visit(parser)
@ -215,7 +215,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
val expression: CriteriaExpression<L, Boolean>,
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
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() {
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
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.internal.declaredField
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.serialization.CordaSerializable
import java.lang.reflect.Field
@ -24,7 +28,9 @@ enum class BinaryLogicalOperator : Operator {
enum class EqualityComparisonOperator : Operator {
EQUAL,
NOT_EQUAL
NOT_EQUAL,
EQUAL_IGNORE_CASE,
NOT_EQUAL_IGNORE_CASE
}
enum class BinaryComparisonOperator : Operator {
@ -41,12 +47,16 @@ enum class NullOperator : Operator {
enum class LikenessOperator : Operator {
LIKE,
NOT_LIKE
NOT_LIKE,
LIKE_IGNORE_CASE,
NOT_LIKE_IGNORE_CASE
}
enum class CollectionOperator : Operator {
IN,
NOT_IN
NOT_IN,
IN_IGNORE_CASE,
NOT_IN_IGNORE_CASE
}
@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>> 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))
fun <O, R> KProperty1<O, R?>.notEqual(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.NOT_EQUAL, value))
@JvmOverloads
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?>.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?>.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?>.`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
@JvmOverloads
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
fun <R> Field.equal(value: R) = info().equal(value)
@JvmStatic
fun <R> FieldInfo.equal(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.EQUAL, value))
fun <R> Field.equal(value: R, exactMatch: Boolean = true) = info().equal(value, exactMatch)
@JvmStatic
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
fun <R> Field.notEqual(value: R) = info().notEqual(value)
@JvmOverloads
fun <R> FieldInfo.equal(value: R, exactMatch: Boolean = true) = predicate(Builder.equal(value, exactMatch))
@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
@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))
@JvmStatic
@JvmOverloads
@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>> FieldInfo.`in`(collection: Collection<R>) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.IN, collection))
fun <R : Comparable<R>> Field.`in`(collection: Collection<R>, exactMatch: Boolean = true) = info().`in`(collection, exactMatch)
@JvmStatic
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
fun <R : Comparable<R>> Field.notIn(collection: Collection<R>) = info().notIn(collection)
@JvmStatic
fun <R : Comparable<R>> FieldInfo.notIn(collection: Collection<R>) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.NOT_IN, collection))
@JvmOverloads
fun <R : Comparable<R>> FieldInfo.`in`(collection: Collection<R>, exactMatch: Boolean = true) = predicate(Builder.`in`(collection, exactMatch))
@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>> 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>> 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>> `in`(collection: Collection<R>) = ColumnPredicate.CollectionExpression(CollectionOperator.IN, collection)
fun <R : Comparable<R>> notIn(collection: Collection<R>) = ColumnPredicate.CollectionExpression(CollectionOperator.NOT_IN, collection)
fun like(string: String) = ColumnPredicate.Likeness(LikenessOperator.LIKE, string)
fun notLike(string: String) = ColumnPredicate.Likeness(LikenessOperator.NOT_LIKE, string)
@JvmOverloads
fun <R : Comparable<R>> `in`(collection: Collection<R>, exactMatch: Boolean = true) = CollectionExpression(if (exactMatch) IN else IN_IGNORE_CASE, collection)
@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> 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
@JvmOverloads
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
fun Field.like(string: String) = info().like(string)
@JvmStatic
fun FieldInfo.like(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.LIKE, string))
fun Field.like(string: String, exactMatch: Boolean = true) = info().like(string, exactMatch)
fun <O> KProperty1<O, String?>.notLike(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.NOT_LIKE, string))
@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.")
fun Field.notLike(string: String) = info().notLike(string)
fun Field.notLike(string: String, exactMatch: Boolean = true) = info().notLike(string, exactMatch)
@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))
@JvmStatic

View File

@ -207,7 +207,13 @@ interface SerializationContext {
* The use case that we are serializing for, since it influences the implementations chosen.
*/
@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_CLIENT_CONTEXT get() = effectiveSerializationEnv.rpcClientContext
@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
interface SerializationEnvironment {
val serializationFactory: SerializationFactory
val checkpointSerializationFactory: CheckpointSerializationFactory
val p2pContext: SerializationContext
val rpcServerContext: SerializationContext
val rpcClientContext: SerializationContext
val storageContext: SerializationContext
val checkpointContext: SerializationContext
val checkpointContext: CheckpointSerializationContext
}
@KeepForDJVM
@ -26,18 +27,21 @@ open class SerializationEnvironmentImpl(
rpcServerContext: SerializationContext? = null,
rpcClientContext: 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:
override lateinit var rpcServerContext: SerializationContext
override lateinit var rpcClientContext: SerializationContext
override lateinit var storageContext: SerializationContext
override lateinit var checkpointContext: SerializationContext
override lateinit var checkpointContext: CheckpointSerializationContext
override lateinit var checkpointSerializationFactory: CheckpointSerializationFactory
init {
rpcServerContext?.let { this.rpcServerContext = it }
rpcClientContext?.let { this.rpcClientContext = it }
storageContext?.let { this.storageContext = 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.KeepForDJVM
import net.corda.core.contracts.*
import net.corda.core.contracts.ComponentGroupEnum.*
import net.corda.core.crypto.*
import net.corda.core.identity.Party
import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.*
import net.corda.core.utilities.OpaqueBytes
import java.security.PublicKey
import java.util.function.Predicate
import kotlin.reflect.KClass
/**
* 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() {
/** 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). */
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). */
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. */
val commands: List<Command<*>> = deserialiseCommands()
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." }
if (notaries.isNotEmpty()) notaries[0] else null
notaries.firstOrNull()
}
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." }
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.
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 }
return if (group != null && group.components.isNotEmpty()) {
group.components.mapIndexed { internalIndex, component ->
try {
deserialiseBody(component.bytes)
factory.deserialize(component, clazz.java, context)
} catch (e: MissingAttachmentsException) {
throw e
} catch (e: Exception) {
@ -87,11 +94,13 @@ abstract class TraversableTransaction(open val componentGroups: List<ComponentGr
// TODO: we could avoid deserialising unrelated signers.
// 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).
val signersList = deserialiseComponentGroup(ComponentGroupEnum.SIGNERS_GROUP, { SerializedBytes<List<PublicKey>>(it).deserialize() })
val commandDataList = deserialiseComponentGroup(ComponentGroupEnum.COMMANDS_GROUP, { SerializedBytes<CommandData>(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) })
val group = componentGroups.firstOrNull { it.groupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal }
val signersList: List<List<PublicKey>> = uncheckedCast(deserialiseComponentGroup(List::class, SIGNERS_GROUP))
val commandDataList: List<CommandData> = deserialiseComponentGroup(CommandData::class, COMMANDS_GROUP, attachmentsContext = true)
val group = componentGroups.firstOrNull { it.groupIndex == COMMANDS_GROUP.ordinal }
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 leafIndices = componentHashes.map { group.partialMerkleTree.leafIndex(it) }
if (leafIndices.isNotEmpty())
@ -100,7 +109,9 @@ abstract class TraversableTransaction(open val componentGroups: List<ComponentGr
} else {
// It is a WireTransaction
// 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]) }
}
}
@ -145,47 +156,47 @@ class FilteredTransaction internal constructor(
var signersIncluded = false
fun <T : Any> filter(t: T, componentGroupIndex: Int, internalIndex: Int) {
if (filtering.test(t)) {
val group = filteredSerialisedComponents[componentGroupIndex]
// 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.
val serialisedComponent = wtx.componentGroups.first { it.groupIndex == componentGroupIndex }.components[internalIndex]
if (group == null) {
// As all of the helper Map structures, like availableComponentNonces, availableComponentHashes
// and groupsMerkleRoots, are computed lazily via componentGroups.forEach, there should always be
// a match on Map.get ensuring it will never return null.
filteredSerialisedComponents[componentGroupIndex] = mutableListOf(serialisedComponent)
filteredComponentNonces[componentGroupIndex] = mutableListOf(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex])
filteredComponentHashes[componentGroupIndex] = mutableListOf(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex])
} else {
group.add(serialisedComponent)
// If the group[componentGroupIndex] existed, then we guarantee that
// filteredComponentNonces[componentGroupIndex] and filteredComponentHashes[componentGroupIndex] are not null.
filteredComponentNonces[componentGroupIndex]!!.add(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex])
filteredComponentHashes[componentGroupIndex]!!.add(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex])
}
// If at least one command is visible, then all command-signers should be visible as well.
// This is required for visibility purposes, see FilteredTransaction.checkAllCommandsVisible() for more details.
if (componentGroupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal && !signersIncluded) {
signersIncluded = true
val signersGroupIndex = ComponentGroupEnum.SIGNERS_GROUP.ordinal
// There exist commands, thus the signers group is not empty.
val signersGroupComponents = wtx.componentGroups.first { it.groupIndex == signersGroupIndex }
filteredSerialisedComponents[signersGroupIndex] = signersGroupComponents.components.toMutableList()
filteredComponentNonces[signersGroupIndex] = wtx.availableComponentNonces[signersGroupIndex]!!.toMutableList()
filteredComponentHashes[signersGroupIndex] = wtx.availableComponentHashes[signersGroupIndex]!!.toMutableList()
}
if (!filtering.test(t)) return
val group = filteredSerialisedComponents[componentGroupIndex]
// Because the filter passed, we know there is a match. We also use first Vs single as the init function
// of WireTransaction ensures there are no duplicated groups.
val serialisedComponent = wtx.componentGroups.first { it.groupIndex == componentGroupIndex }.components[internalIndex]
if (group == null) {
// As all of the helper Map structures, like availableComponentNonces, availableComponentHashes
// and groupsMerkleRoots, are computed lazily via componentGroups.forEach, there should always be
// a match on Map.get ensuring it will never return null.
filteredSerialisedComponents[componentGroupIndex] = mutableListOf(serialisedComponent)
filteredComponentNonces[componentGroupIndex] = mutableListOf(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex])
filteredComponentHashes[componentGroupIndex] = mutableListOf(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex])
} else {
group.add(serialisedComponent)
// If the group[componentGroupIndex] existed, then we guarantee that
// filteredComponentNonces[componentGroupIndex] and filteredComponentHashes[componentGroupIndex] are not null.
filteredComponentNonces[componentGroupIndex]!!.add(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex])
filteredComponentHashes[componentGroupIndex]!!.add(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex])
}
// If at least one command is visible, then all command-signers should be visible as well.
// This is required for visibility purposes, see FilteredTransaction.checkAllCommandsVisible() for more details.
if (componentGroupIndex == COMMANDS_GROUP.ordinal && !signersIncluded) {
signersIncluded = true
val signersGroupIndex = SIGNERS_GROUP.ordinal
// There exist commands, thus the signers group is not empty.
val signersGroupComponents = wtx.componentGroups.first { it.groupIndex == signersGroupIndex }
filteredSerialisedComponents[signersGroupIndex] = signersGroupComponents.components.toMutableList()
filteredComponentNonces[signersGroupIndex] = wtx.availableComponentNonces[signersGroupIndex]!!.toMutableList()
filteredComponentHashes[signersGroupIndex] = wtx.availableComponentHashes[signersGroupIndex]!!.toMutableList()
}
}
fun updateFilteredComponents() {
wtx.inputs.forEachIndexed { internalIndex, it -> filter(it, ComponentGroupEnum.INPUTS_GROUP.ordinal, internalIndex) }
wtx.outputs.forEachIndexed { internalIndex, it -> filter(it, ComponentGroupEnum.OUTPUTS_GROUP.ordinal, internalIndex) }
wtx.commands.forEachIndexed { internalIndex, it -> filter(it, ComponentGroupEnum.COMMANDS_GROUP.ordinal, internalIndex) }
wtx.attachments.forEachIndexed { internalIndex, it -> filter(it, ComponentGroupEnum.ATTACHMENTS_GROUP.ordinal, internalIndex) }
if (wtx.notary != null) filter(wtx.notary, ComponentGroupEnum.NOTARY_GROUP.ordinal, 0)
if (wtx.timeWindow != null) filter(wtx.timeWindow, ComponentGroupEnum.TIMEWINDOW_GROUP.ordinal, 0)
wtx.references.forEachIndexed { internalIndex, it -> filter(it, ComponentGroupEnum.REFERENCES_GROUP.ordinal, internalIndex) }
wtx.inputs.forEachIndexed { internalIndex, it -> filter(it, INPUTS_GROUP.ordinal, internalIndex) }
wtx.outputs.forEachIndexed { internalIndex, it -> filter(it, OUTPUTS_GROUP.ordinal, internalIndex) }
wtx.commands.forEachIndexed { internalIndex, it -> filter(it, COMMANDS_GROUP.ordinal, internalIndex) }
wtx.attachments.forEachIndexed { internalIndex, it -> filter(it, ATTACHMENTS_GROUP.ordinal, internalIndex) }
if (wtx.notary != null) filter(wtx.notary, NOTARY_GROUP.ordinal, 0)
if (wtx.timeWindow != null) filter(wtx.timeWindow, TIMEWINDOW_GROUP.ordinal, 0)
wtx.references.forEachIndexed { internalIndex, it -> filter(it, REFERENCES_GROUP.ordinal, internalIndex) }
// It is highlighted that because there is no a signers property in TraversableTransaction,
// 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
@ -195,10 +206,17 @@ class FilteredTransaction internal constructor(
// 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,
// 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> {
updateFilteredComponents()
@ -223,8 +241,11 @@ class FilteredTransaction internal constructor(
@Throws(FilteredTransactionVerificationException::class)
fun verify() {
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).
verificationCheck(MerkleTree.getMerkleTree(groupHashes).hash == id) { "Top level Merkle tree cannot be verified against transaction's id" }
// Verify the top level Merkle tree (group hashes are its leaves, including allOnesHash for empty list or null
// 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).
if (filteredComponentGroups.isEmpty()) return
@ -233,8 +254,12 @@ class FilteredTransaction internal constructor(
filteredComponentGroups.forEach { (groupIndex, components, nonces, groupPartialTree) ->
verificationCheck(groupIndex < groupHashes.size) { "There is no matching component group hash for group $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(groupPartialTree.verify(groupMerkleRoot, components.mapIndexed { index, component -> componentHash(nonces[index], component) })) { "Visible components in group $groupIndex cannot be verified against their partial Merkle tree" }
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(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
visibilityCheck(groupPartialRoot == groupFullRoot) { "Some components for group ${group.groupIndex} are not visible" }
// 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)
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 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.
private fun expectedNumOfCommands(publicKey: PublicKey, commandSigners: ComponentGroup?): Int {
checkAllComponentsVisible(ComponentGroupEnum.SIGNERS_GROUP)
checkAllComponentsVisible(SIGNERS_GROUP)
if (commandSigners == null) return 0
fun signersKeys (internalIndex: Int, opaqueBytes: OpaqueBytes): List<PublicKey> {
try {
@ -340,7 +369,10 @@ class FilteredTransaction internal constructor(
*/
@KeepForDJVM
@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 {
check(components.size == nonces.size) { "Size of transaction components and nonces do not match" }
}

View File

@ -33,9 +33,10 @@ sealed class ByteSequence(private val _bytes: ByteArray, val offset: Int, val si
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.
*/
@Suppress("MemberVisibilityCanBePrivate")
@ -43,7 +44,7 @@ sealed class ByteSequence(private val _bytes: ByteArray, val offset: Int, val si
require(offset >= 0)
require(offset + size <= this.size)
// 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 {

View File

@ -106,7 +106,7 @@ class CordappSmokeTest {
class SendBackInitiatorFlowContext(private val otherPartySession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
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()
otherPartySession.send(sessionInitContext)
}

View File

@ -1,11 +1,14 @@
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.SerializationFactory;
import net.corda.testing.core.SerializationEnvironmentRule;
import org.junit.Rule;
import org.junit.Test;
import static net.corda.core.serialization.internal.CheckpointSerializationAPIKt.checkpointSerialize;
import static net.corda.core.serialization.SerializationAPIKt.serialize;
import static org.junit.Assert.assertNull;
@ -28,10 +31,13 @@ public class SerializationApiInJavaTest {
public void enforceSerializationDefaultsApi() {
SerializationDefaults defaults = SerializationDefaults.INSTANCE;
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.getRPC_SERVER_CONTEXT());
serialize("hello", factory, defaults.getRPC_CLIENT_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 query = QueryCriteria.LinearStateQueryCriteria(
linearId = listOf(linearId),
isRelevant = Vault.RelevancyStatus.ALL
relevancyStatus = Vault.RelevancyStatus.ALL
)
val referenceState = serviceHub.vaultService.queryBy<ContractState>(query).states.single()
return subFlow(FinalityFlow(

View File

@ -8,7 +8,7 @@ import kotlin.test.assertFailsWith
class CertRoleTests {
@Test
fun `should deserialize valid value`() {
val expected = CertRole.INTERMEDIATE_CA
val expected = CertRole.DOORMAN_CA
val actual = CertRole.getInstance(ASN1Integer(1L))
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 net.corda.core.crypto.random63BitValue
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.kryoMagic
import net.corda.serialization.internal.SerializationContextImpl
import net.corda.serialization.internal.CheckpointSerializationContextImpl
import net.corda.testing.core.SerializationEnvironmentRule
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
@ -24,12 +25,11 @@ class KotlinUtilsTest {
@Rule
val expectedEx: ExpectedException = ExpectedException.none()
private val KRYO_CHECKPOINT_NOWHITELIST_CONTEXT = SerializationContextImpl(kryoMagic,
private val KRYO_CHECKPOINT_NOWHITELIST_CONTEXT = CheckpointSerializationContextImpl(
javaClass.classLoader,
EmptyWhitelist,
emptyMap(),
true,
SerializationContext.UseCase.Checkpoint,
null)
@Test
@ -44,7 +44,7 @@ class KotlinUtilsTest {
fun `checkpointing a transient property with non-capturing lambda`() {
val original = NonCapturingTransientProperty()
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
assertThat(copyVal).isNotEqualTo(originalVal)
assertThat(copy.transientVal).isEqualTo(copyVal)
@ -55,14 +55,14 @@ class KotlinUtilsTest {
expectedEx.expect(KryoException::class.java)
expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization")
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
fun `checkpointing a transient property with capturing lambda`() {
val original = CapturingTransientProperty("Hello")
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
assertThat(copyVal).isNotEqualTo(originalVal)
assertThat(copy.transientVal).isEqualTo(copyVal)
@ -76,7 +76,7 @@ class KotlinUtilsTest {
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 {

View File

@ -1,18 +1,35 @@
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 {
// 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 {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$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 "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
shadow "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
shadow "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
shadow "org.slf4j:slf4j-api:$slf4j_version"
// ASM: byte code manipulation library
compile "org.ow2.asm:asm:$asm_version"
@ -20,30 +37,40 @@ dependencies {
compile "org.ow2.asm:asm-commons:$asm_version"
// 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
testCompile "junit:junit:$junit_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
shadowJar {
baseName = "djvm"
classifier = ""
dependencies {
exclude(dependency('com.jcabi:.*:.*'))
exclude(dependency('org.apache.*:.*:.*'))
exclude(dependency('org.jetbrains.*:.*:.*'))
exclude(dependency('org.slf4j:.*:.*'))
exclude(dependency('io.github.lukehutch:.*:.*'))
}
baseName 'corda-djvm'
classifier ''
relocate 'org.objectweb.asm', 'djvm.org.objectweb.asm'
artifacts {
shadow(tasks.shadowJar.archivePath) {
builtBy 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 {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$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 "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
compile "info.picocli:picocli:$picocli_version"
compile "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version"
compile project(path: ":djvm", configuration: "shadow")
// Deterministic runtime - used in whitelist generation

View File

@ -1,8 +1,5 @@
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.Parameters
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.source.ClassSource
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 djvm.org.objectweb.asm.ClassReader
import picocli.CommandLine.Option
@ -66,7 +63,7 @@ abstract class ClassCommand : CommandBase() {
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
@ -117,7 +114,7 @@ abstract class ClassCommand : CommandBase() {
}
private fun findDiscoverableRunnables(filters: Array<String>): List<Class<*>> {
val classes = find<DiscoverableRunnable>()
val classes = find<java.util.function.Function<*,*>>()
val applicableFilters = filters
.filter { !isJarFile(it) && !isFullClassName(it) }
val filteredClasses = applicableFilters
@ -128,7 +125,7 @@ abstract class ClassCommand : CommandBase() {
}
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(" ")}'")
}
@ -192,7 +189,7 @@ abstract class ClassCommand : CommandBase() {
profile = profile,
rules = if (ignoreRules) { emptyList() } else { Discovery.find() },
emitters = ignoreEmitters.emptyListIfTrueOtherwiseNull(),
definitionProviders = if(ignoreDefinitionProviders) { emptyList() } else { Discovery.find() },
definitionProviders = if (ignoreDefinitionProviders) { emptyList() } else { Discovery.find() },
enableTracing = !disableTracing,
analysisConfiguration = AnalysisConfiguration(
whitelist = whitelist,

View File

@ -1,7 +1,6 @@
package net.corda.djvm.tools.cli
import net.corda.djvm.source.ClassSource
import net.corda.djvm.tools.Utilities.createCodePath
import picocli.CommandLine.Command
import picocli.CommandLine.Parameters
import java.nio.file.Files

View File

@ -1,9 +1,5 @@
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 java.nio.file.Files
import java.nio.file.Path

View File

@ -1,6 +1,5 @@
package net.corda.djvm.tools.cli
import net.corda.djvm.execution.SandboxedRunnable
import net.corda.djvm.source.ClassSource
import picocli.CommandLine.Command
import picocli.CommandLine.Parameters
@ -20,7 +19,7 @@ class RunCommand : ClassCommand() {
var classes: Array<String> = emptyArray()
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) {
if (!clazz.interfaces.any { it.simpleName == interfaceName }) {
printError("Class is not an instance of $interfaceName; ${clazz.name}")

View File

@ -1,6 +1,5 @@
package net.corda.djvm.tools.cli
import net.corda.djvm.tools.Utilities.workingDirectory
import picocli.CommandLine.Command
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,52 +33,53 @@ class WhitelistGenerateCommand : CommandBase() {
override fun validateArguments() = paths.isNotEmpty()
override fun handleCommand(): Boolean {
val entries = mutableListOf<String>()
val visitor = object : ClassAndMemberVisitor() {
override fun visitClass(clazz: ClassRepresentation): ClassRepresentation {
entries.add(clazz.name)
return super.visitClass(clazz)
}
val entries = AnalysisConfiguration().use { configuration ->
val entries = mutableListOf<String>()
val visitor = object : ClassAndMemberVisitor(configuration, null) {
override fun visitClass(clazz: ClassRepresentation): ClassRepresentation {
entries.add(clazz.name)
return super.visitClass(clazz)
}
override fun visitMethod(clazz: ClassRepresentation, method: Member): Member {
visitMember(clazz, method)
return super.visitMethod(clazz, method)
}
override fun visitMethod(clazz: ClassRepresentation, method: Member): Member {
visitMember(clazz, method)
return super.visitMethod(clazz, method)
}
override fun visitField(clazz: ClassRepresentation, field: Member): Member {
visitMember(clazz, field)
return super.visitField(clazz, field)
}
override fun visitField(clazz: ClassRepresentation, field: Member): Member {
visitMember(clazz, field)
return super.visitField(clazz, field)
}
private fun visitMember(clazz: ClassRepresentation, member: Member) {
entries.add("${clazz.name}.${member.memberName}:${member.signature}")
private fun visitMember(clazz: ClassRepresentation, member: Member) {
entries.add("${clazz.name}.${member.memberName}:${member.signature}")
}
}
val context = AnalysisContext.fromConfiguration(configuration)
for (path in paths) {
ClassSource.fromPath(path).getStreamIterator().forEach {
visitor.analyze(it, context)
}
}
entries
}
val context = AnalysisContext.fromConfiguration(AnalysisConfiguration(), emptyList())
for (path in paths) {
ClassSource.fromPath(path).getStreamIterator().forEach {
visitor.analyze(it, context)
}
}
val output = output
if (output != null) {
Files.newOutputStream(output, StandardOpenOption.CREATE).use {
GZIPOutputStream(it).use {
PrintStream(it).use {
it.println("""
output?.also {
Files.newOutputStream(it, StandardOpenOption.CREATE).use { out ->
GZIPOutputStream(out).use { gzip ->
PrintStream(gzip).use { pout ->
pout.println("""
|java/.*
|javax/.*
|jdk/.*
|com/sun/.*
|sun/.*
|---
""".trimMargin().trim())
printEntries(it, entries)
printEntries(pout, entries)
}
}
}
} else {
printEntries(System.out, entries)
}
} ?: printEntries(System.out, entries)
return true
}

View File

@ -3,25 +3,20 @@ package net.corda.djvm
import net.corda.djvm.analysis.AnalysisContext
import net.corda.djvm.costing.RuntimeCostSummary
import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.djvm.source.ClassSource
/**
* The context in which a sandboxed operation is run.
*
* @property configuration The configuration of the sandbox.
* @property inputClasses The classes passed in for analysis.
*/
class SandboxRuntimeContext(
val configuration: SandboxConfiguration,
private val inputClasses: List<ClassSource>
) {
class SandboxRuntimeContext(val configuration: SandboxConfiguration) {
/**
* The class loader to use inside the sandbox.
*/
val classLoader: SandboxClassLoader = SandboxClassLoader(
configuration,
AnalysisContext.fromConfiguration(configuration.analysisConfiguration, inputClasses)
AnalysisContext.fromConfiguration(configuration.analysisConfiguration)
)
/**
@ -35,7 +30,7 @@ class SandboxRuntimeContext(
fun use(action: SandboxRuntimeContext.() -> Unit) {
SandboxRuntimeContext.instance = this
try {
this.action()
action(this)
} finally {
threadLocalContext.remove()
}
@ -43,9 +38,7 @@ class SandboxRuntimeContext(
companion object {
private val threadLocalContext = object : ThreadLocal<SandboxRuntimeContext?>() {
override fun initialValue(): SandboxRuntimeContext? = null
}
private val threadLocalContext = ThreadLocal<SandboxRuntimeContext?>()
/**
* 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
import net.corda.djvm.code.ruleViolationError
import net.corda.djvm.code.thresholdViolationError
import net.corda.djvm.messages.Severity
import net.corda.djvm.references.ClassModule
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 java.io.Closeable
import java.io.IOException
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
* made available inside the sandboxed environment.
* @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 prefixFilters Only record messages where the originating class name matches one of the provided prefixes.
* If none are provided, all messages will be reported.
@ -24,32 +31,47 @@ class AnalysisConfiguration(
val whitelist: Whitelist = Whitelist.MINIMAL,
additionalPinnedClasses: Set<String> = emptySet(),
val minimumSeverityLevel: Severity = Severity.WARNING,
val classPath: List<Path> = emptyList(),
classPath: List<Path> = emptyList(),
bootstrapJar: Path? = null,
val analyzeAnnotations: Boolean = false,
val prefixFilters: List<String> = emptyList(),
val classModule: ClassModule = ClassModule(),
val memberModule: MemberModule = MemberModule()
) {
) : Closeable {
/**
* Classes that have already been declared in the sandbox namespace and that should be made
* 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.
*/
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 {
/**
* The package name prefix to use for classes loaded into a sandbox.
*/
private const val SANDBOX_PREFIX: String = "sandbox/"
private const val SANDBOXED_OBJECT = "sandbox/java/lang/Object"
private const val RUNTIME_COST_ACCOUNTER = RuntimeCostAccounter.TYPE_NAME
private const val SANDBOXED_OBJECT = SANDBOX_PREFIX + "java/lang/Object"
}
}

View File

@ -1,10 +1,10 @@
package net.corda.djvm.analysis
import net.corda.djvm.code.asPackagePath
import net.corda.djvm.messages.MessageCollection
import net.corda.djvm.references.ClassHierarchy
import net.corda.djvm.references.EntityReference
import net.corda.djvm.references.ReferenceMap
import net.corda.djvm.source.ClassSource
/**
* 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 references A collection of all referenced members found during analysis together with the locations from
* where each member has been accessed or invoked.
* @property inputClasses The classes passed in for analysis.
*/
class AnalysisContext private constructor(
val messages: MessageCollection,
val classes: ClassHierarchy,
val references: ReferenceMap,
val inputClasses: List<ClassSource>
val references: ReferenceMap
) {
private val origins = mutableMapOf<String, MutableSet<EntityReference>>()
@ -28,7 +26,7 @@ class AnalysisContext private constructor(
* Record a class origin in the current analysis context.
*/
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.
*/
fun fromConfiguration(configuration: AnalysisConfiguration, classes: List<ClassSource>): AnalysisContext {
fun fromConfiguration(configuration: AnalysisConfiguration): AnalysisContext {
return AnalysisContext(
MessageCollection(configuration.minimumSeverityLevel, configuration.prefixFilters),
ClassHierarchy(configuration.classModule, configuration.memberModule),
ReferenceMap(configuration.classModule),
classes
ReferenceMap(configuration.classModule)
)
}
/**
* 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.instructions.*
import net.corda.djvm.messages.Message
import net.corda.djvm.references.ClassReference
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 net.corda.djvm.references.*
import org.objectweb.asm.*
import java.io.InputStream
/**
* 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 classVisitor Class visitor to use when traversing the structure of classes.
*/
open class ClassAndMemberVisitor(
private val classVisitor: ClassVisitor? = null,
private val configuration: AnalysisConfiguration = AnalysisConfiguration()
private val configuration: AnalysisConfiguration,
private val classVisitor: ClassVisitor?
) {
/**
* Holds a reference to the currently used analysis context.
*/
protected var analysisContext: AnalysisContext =
AnalysisContext.fromConfiguration(configuration, emptyList())
protected var analysisContext: AnalysisContext = AnalysisContext.fromConfiguration(configuration)
/**
* Holds a link to the class currently being traversed.
@ -44,12 +39,6 @@ open class ClassAndMemberVisitor(
*/
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.
*/
@ -63,7 +52,7 @@ open class ClassAndMemberVisitor(
* @param origin The originating class for the analysis.
*/
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)
}
}
@ -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 {
return try {
@ -229,9 +219,7 @@ open class ClassAndMemberVisitor(
ClassRepresentation(version, access, name, superClassName, interfaceNames, genericsDetails = signature ?: "").also {
currentClass = it
currentMember = null
sourceLocation = SourceLocation(
className = name
)
sourceLocation = SourceLocation(className = name)
}
captureExceptions {
currentClass = visitClass(currentClass!!)
@ -251,7 +239,7 @@ open class ClassAndMemberVisitor(
override fun visitEnd() {
configuration.classModule
.getClassReferencesFromClass(currentClass!!, configuration.analyzeAnnotations)
.forEach { recordTypeReference(it) }
.forEach(::recordTypeReference)
captureExceptions {
visitClassEnd(currentClass!!)
}
@ -306,14 +294,15 @@ open class ClassAndMemberVisitor(
configuration.memberModule.addToClass(clazz, visitedMember ?: member)
return if (processMember) {
val derivedMember = visitedMember ?: member
val targetVisitor = super.visitMethod(
derivedMember.access,
derivedMember.memberName,
derivedMember.signature,
signature,
derivedMember.exceptions.toTypedArray()
)
MethodVisitorImpl(targetVisitor)
super.visitMethod(
derivedMember.access,
derivedMember.memberName,
derivedMember.signature,
signature,
derivedMember.exceptions.toTypedArray()
)?.let { targetVisitor ->
MethodVisitorImpl(targetVisitor, derivedMember)
}
} else {
null
}
@ -340,14 +329,15 @@ open class ClassAndMemberVisitor(
configuration.memberModule.addToClass(clazz, visitedMember ?: member)
return if (processMember) {
val derivedMember = visitedMember ?: member
val targetVisitor = super.visitField(
derivedMember.access,
derivedMember.memberName,
derivedMember.signature,
signature,
derivedMember.value
)
FieldVisitorImpl(targetVisitor)
super.visitField(
derivedMember.access,
derivedMember.memberName,
derivedMember.signature,
signature,
derivedMember.value
)?.let { targetVisitor ->
FieldVisitorImpl(targetVisitor)
}
} else {
null
}
@ -359,7 +349,8 @@ open class ClassAndMemberVisitor(
* Visitor used to traverse and analyze a method.
*/
private inner class MethodVisitorImpl(
targetVisitor: MethodVisitor?
targetVisitor: MethodVisitor,
private val method: Member
) : MethodVisitor(API_VERSION, targetVisitor) {
/**
@ -387,6 +378,16 @@ open class ClassAndMemberVisitor(
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.
*/
@ -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.
*/
@ -517,7 +541,7 @@ open class ClassAndMemberVisitor(
* Visitor used to traverse and analyze a field.
*/
private inner class FieldVisitorImpl(
targetVisitor: FieldVisitor?
targetVisitor: FieldVisitor
) : FieldVisitor(API_VERSION, targetVisitor) {
/**

View File

@ -1,5 +1,8 @@
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.
*
@ -32,12 +35,12 @@ class ClassResolver(
*/
fun resolve(name: String): String {
return when {
name.startsWith("[") && name.endsWith(";") -> {
name.startsWith('[') && name.endsWith(';') -> {
complexArrayTypeRegex.replace(name) {
"${it.groupValues[1]}L${resolveName(it.groupValues[2])};"
}
}
name.startsWith("[") && !name.endsWith(";") -> name
name.startsWith('[') && !name.endsWith(';') -> name
else -> resolveName(name)
}
}
@ -46,7 +49,7 @@ class ClassResolver(
* Resolve the class name from a fully qualified normalized name.
*/
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.
*/
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/Void(\\..*)?$".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 definitionProviders: List<DefinitionProvider> = 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.
@ -82,7 +82,8 @@ class ClassMutator(
*/
override fun visitInstruction(method: Member, emitter: EmitterModule, instruction: Instruction) {
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)
}
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.
*/
@JvmDefault
val isTracer: Boolean
get() = false

View File

@ -1,7 +1,9 @@
package net.corda.djvm.code
import org.objectweb.asm.Label
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
/**
@ -29,7 +31,7 @@ class EmitterModule(
/**
* 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
methodVisitor.visitTypeInsn(opcode, typeName)
}
@ -38,7 +40,7 @@ class EmitterModule(
* Emit instruction for creating a new object of type [T].
*/
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) {
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) {
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.
*/
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() {
hasEmittedCustomCode = true
methodVisitor.visitInsn(Opcodes.POP)
methodVisitor.visitInsn(POP)
}
/**
@ -93,19 +95,40 @@ class EmitterModule(
*/
fun duplicate() {
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.
*/
fun throwError(message: String) {
fun <T : Throwable> throwException(exceptionType: Class<T>, message: String) {
hasEmittedCustomCode = true
new<java.lang.Exception>()
methodVisitor.visitInsn(Opcodes.DUP)
val exceptionName = Type.getInternalName(exceptionType)
new(exceptionName)
methodVisitor.visitInsn(DUP)
methodVisitor.visitLdcInsn(message)
invokeSpecial<java.lang.Exception>("<init>", "(Ljava/lang/String;)V")
methodVisitor.visitInsn(Opcodes.ATHROW)
invokeSpecial(exceptionName, "<init>", "(Ljava/lang/String;)V")
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
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.
@ -41,7 +42,7 @@ open class TypedRuntimeCost<T>(
if (thresholdPredicate(newValue)) {
val message = errorMessage(currentThread)
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.source.ClassSource
import java.util.function.Function
/**
* 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.
*/
class DeterministicSandboxExecutor<TInput, TOutput>(
configuration: SandboxConfiguration = SandboxConfiguration.DEFAULT
configuration: SandboxConfiguration
) : 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):
ExecutionSummaryWithResult<TOutput?> {
inline fun <reified TRunnable : Function<in TInput, out TOutput>> run(input: TInput):
ExecutionSummaryWithResult<TOutput> {
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
/**
* 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 invocationCostThreshold The threshold placed on invocations.

View File

@ -1,9 +1,9 @@
package net.corda.djvm.execution
/**
* The summary of the execution of a [SandboxedRunnable] in a sandbox. This class has no representation of the outcome,
* and is typically used when there has been a pre-mature exit from the sandbox, for instance, if an exception was
* thrown.
* The summary of the execution of a [java.util.function.Function] in a sandbox. This class has no representation of the
* outcome, and is typically used when there has been a pre-mature exit from the sandbox, for instance, if an exception
* was thrown.
*
* @property costs The costs accumulated when running the sandboxed code.
*/

View File

@ -1,7 +1,7 @@
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.
* @see ExecutionSummary

View File

@ -2,7 +2,6 @@ package net.corda.djvm.execution
import net.corda.djvm.SandboxConfiguration
import net.corda.djvm.SandboxRuntimeContext
import net.corda.djvm.analysis.AnalysisContext
import net.corda.djvm.messages.MessageCollection
import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.djvm.rewiring.SandboxClassLoadingException
@ -16,8 +15,7 @@ import kotlin.concurrent.thread
*/
class IsolatedTask(
private val identifier: String,
private val configuration: SandboxConfiguration,
private val context: AnalysisContext
private val configuration: SandboxConfiguration
) {
/**
@ -32,12 +30,12 @@ class IsolatedTask(
var exception: Throwable? = null
thread(name = threadName, isDaemon = true) {
logger.trace("Entering isolated runtime environment...")
SandboxRuntimeContext(configuration, context.inputClasses).use {
SandboxRuntimeContext(configuration).use {
output = try {
action(runnable)
} catch (ex: Throwable) {
logger.error("Exception caught in isolated runtime environment", ex)
exception = ex
exception = (ex as? LinkageError)?.cause ?: ex
null
}
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
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.utilities.loggerFor
import net.corda.djvm.validation.ReferenceValidationSummary
import net.corda.djvm.validation.ReferenceValidator
import java.lang.reflect.InvocationTargetException
/**
@ -22,7 +21,7 @@ import java.lang.reflect.InvocationTargetException
* @property configuration The configuration of sandbox.
*/
open class SandboxExecutor<in TInput, out TOutput>(
protected val configuration: SandboxConfiguration = SandboxConfiguration.DEFAULT
protected val configuration: SandboxConfiguration
) {
private val classModule = configuration.analysisConfiguration.classModule
@ -32,12 +31,7 @@ open class SandboxExecutor<in TInput, out TOutput>(
private val whitelist = configuration.analysisConfiguration.whitelist
/**
* Module used to validate all traversable references before instantiating and executing a [SandboxedRunnable].
*/
private val referenceValidator = ReferenceValidator(configuration.analysisConfiguration)
/**
* Executes a [SandboxedRunnable] implementation.
* Executes a [java.util.function.Function] implementation.
*
* @param runnableClass The entry point of the sandboxed code to run.
* @param input The input to provide to the sandboxed environment.
@ -50,7 +44,7 @@ open class SandboxExecutor<in TInput, out TOutput>(
open fun run(
runnableClass: ClassSource,
input: TInput
): ExecutionSummaryWithResult<TOutput?> {
): ExecutionSummaryWithResult<TOutput> {
// 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.
// 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
// and consequently hit the cache. Once loaded, we can execute the code on the spawned thread, i.e., in an
// 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
// 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.
// 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.
val classSources = listOf(runnableClass)
val context = AnalysisContext.fromConfiguration(configuration.analysisConfiguration, classSources)
val result = IsolatedTask(runnableClass.qualifiedClassName, configuration, context).run {
val context = AnalysisContext.fromConfiguration(configuration.analysisConfiguration)
val result = IsolatedTask(runnableClass.qualifiedClassName, configuration).run {
validate(context, classLoader, classSources)
val loadedClass = classLoader.loadClassAndBytes(runnableClass, context)
val instance = loadedClass.type.newInstance()
val method = loadedClass.type.getMethod("run", Any::class.java)
val method = loadedClass.type.getMethod("apply", Any::class.java)
try {
@Suppress("UNCHECKED_CAST")
method.invoke(instance, input) as? TOutput?
method.invoke(instance, input) as? TOutput
} catch (ex: InvocationTargetException) {
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.
*/
fun load(classSource: ClassSource): LoadedClass {
val context = AnalysisContext.fromConfiguration(configuration.analysisConfiguration, listOf(classSource))
val result = IsolatedTask("LoadClass", configuration, context).run {
val context = AnalysisContext.fromConfiguration(configuration.analysisConfiguration)
val result = IsolatedTask("LoadClass", configuration).run {
classLoader.loadClassAndBytes(classSource, context)
}
return result.output ?: throw ClassNotFoundException(classSource.qualifiedClassName)
@ -125,8 +119,8 @@ open class SandboxExecutor<in TInput, out TOutput>(
@Throws(SandboxClassLoadingException::class)
fun validate(vararg classSources: ClassSource): ReferenceValidationSummary {
logger.trace("Validating {}...", classSources)
val context = AnalysisContext.fromConfiguration(configuration.analysisConfiguration, classSources.toList())
val result = IsolatedTask("Validation", configuration, context).run {
val context = AnalysisContext.fromConfiguration(configuration.analysisConfiguration)
val result = IsolatedTask("Validation", configuration).run {
validate(context, classLoader, classSources.toList())
}
logger.trace("Validation of {} resulted in {}", classSources, result)
@ -172,10 +166,6 @@ open class SandboxExecutor<in TInput, out TOutput>(
}
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)
}
@ -185,7 +175,7 @@ open class SandboxExecutor<in TInput, out TOutput>(
private inline fun processClassQueue(
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))
if (!whitelist.matches(className)) {
action(classSource, className)

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