mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
Merge branch 'release/os/4.8' into ronanb/ENT-6699/update-java-version
This commit is contained in:
commit
89da70a205
@ -1,4 +1,4 @@
|
||||
FROM azul/zulu-openjdk:11
|
||||
FROM azul/zulu-openjdk:11.0.14
|
||||
RUN apt-get update && apt-get install -y curl apt-transport-https \
|
||||
ca-certificates \
|
||||
curl \
|
||||
|
@ -33,6 +33,18 @@ def nexusIqStageChoices = [nexusDefaultIqStage].plus(
|
||||
'operate'
|
||||
].minus([nexusDefaultIqStage]))
|
||||
|
||||
/**
|
||||
* define an empty teamsWebHookURL and if it is a Release Branch
|
||||
* then set it for the Corda 4 Jenkins Connector
|
||||
*/
|
||||
boolean isReleaseBranch = (env.BRANCH_NAME =~ /^release\/os\/.*/)
|
||||
def teamsWebHookURL = ""
|
||||
if (isReleaseBranch){
|
||||
withCredentials([string(credentialsId: 'ms-teams-webhook', variable: 'webhook_url')]) {
|
||||
teamsWebHookURL = "$webhook_url"
|
||||
}
|
||||
}
|
||||
|
||||
pipeline {
|
||||
agent { label 'standard' }
|
||||
|
||||
@ -42,6 +54,18 @@ pipeline {
|
||||
overrideIndexTriggers(false)
|
||||
timeout(time: 3, unit: 'HOURS')
|
||||
buildDiscarder(logRotator(daysToKeepStr: '14', artifactDaysToKeepStr: '14'))
|
||||
office365ConnectorWebhooks([[
|
||||
name : "Corda 4 Jenkins Connector",
|
||||
notifyBackToNormal : true,
|
||||
startNotification : false,
|
||||
notifyFailure : true,
|
||||
notifySuccess : true,
|
||||
notifyNotBuilt : false,
|
||||
notifyAborted : false,
|
||||
notifyRepeatedFailure: true,
|
||||
notifyUnstable : true,
|
||||
url : "${teamsWebHookURL}"
|
||||
]])
|
||||
}
|
||||
|
||||
parameters {
|
||||
|
42
.ci/dev/regression/Jenkinsfile
vendored
42
.ci/dev/regression/Jenkinsfile
vendored
@ -7,6 +7,7 @@
|
||||
/**
|
||||
* Sense environment
|
||||
*/
|
||||
boolean isReleaseBranch = (env.BRANCH_NAME =~ /^release\/os\/.*/)
|
||||
boolean isReleaseTag = (env.TAG_NAME =~ /^release-.*(?<!_JDK11)$/)
|
||||
boolean isInternalRelease = (env.TAG_NAME =~ /^internal-release-.*$/)
|
||||
boolean isReleaseCandidate = (env.TAG_NAME =~ /^(release-.*(RC|HC).*(?<!_JDK11))$/)
|
||||
@ -39,6 +40,17 @@ def nexusIqStageChoices = [nexusDefaultIqStage].plus(
|
||||
'operate'
|
||||
].minus([nexusDefaultIqStage]))
|
||||
|
||||
/**
|
||||
* define an empty teamsWebHookURL and if it is a Release Branch
|
||||
* then set it for the Corda 4 Jenkins Connector
|
||||
*/
|
||||
def teamsWebHookURL = ""
|
||||
if (isReleaseBranch || isReleaseTag){
|
||||
withCredentials([string(credentialsId: 'ms-teams-webhook', variable: 'webhook_url')]) {
|
||||
teamsWebHookURL = "$webhook_url"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common Gradle arguments for all Gradle executions
|
||||
*/
|
||||
@ -61,10 +73,23 @@ pipeline {
|
||||
parallelsAlwaysFailFast()
|
||||
timeout(time: 6, unit: 'HOURS')
|
||||
timestamps()
|
||||
office365ConnectorWebhooks([[
|
||||
name : "Corda 4 Jenkins Connector",
|
||||
notifyBackToNormal : true,
|
||||
startNotification : false,
|
||||
notifyFailure : true,
|
||||
notifySuccess : true,
|
||||
notifyNotBuilt : false,
|
||||
notifyAborted : false,
|
||||
notifyRepeatedFailure: true,
|
||||
notifyUnstable : true,
|
||||
url : "${teamsWebHookURL}"
|
||||
]])
|
||||
}
|
||||
|
||||
parameters {
|
||||
choice choices: nexusIqStageChoices, description: 'NexusIQ stage for code evaluation', name: 'nexusIqStage'
|
||||
booleanParam defaultValue: true, description: 'Run tests during this build?', name: 'DO_TEST'
|
||||
}
|
||||
|
||||
/*
|
||||
@ -91,6 +116,9 @@ pipeline {
|
||||
}
|
||||
|
||||
stage('Stash') {
|
||||
when {
|
||||
expression { params.DO_TEST }
|
||||
}
|
||||
steps {
|
||||
stash name: 'compiled', useDefaultExcludes: false
|
||||
}
|
||||
@ -115,6 +143,10 @@ pipeline {
|
||||
}
|
||||
|
||||
stage('All Tests') {
|
||||
when {
|
||||
expression { params.DO_TEST }
|
||||
beforeAgent true
|
||||
}
|
||||
parallel {
|
||||
stage('Another agent') {
|
||||
agent {
|
||||
@ -321,10 +353,12 @@ pipeline {
|
||||
always {
|
||||
script {
|
||||
try {
|
||||
unstash 'allure-input'
|
||||
allure includeProperties: false,
|
||||
jdk: '',
|
||||
results: [[path: '**/allure-input']]
|
||||
if (params.DO_TEST) {
|
||||
unstash 'allure-input'
|
||||
allure includeProperties: false,
|
||||
jdk: '',
|
||||
results: [[path: '**/allure-input']]
|
||||
}
|
||||
} catch (err) {
|
||||
echo("Allure report generation failed: $err")
|
||||
|
||||
|
@ -68,7 +68,7 @@ buildscript {
|
||||
ext.servlet_version = '4.0.1'
|
||||
ext.assertj_version = '3.12.2'
|
||||
ext.slf4j_version = '1.7.30'
|
||||
ext.log4j_version = '2.13.3'
|
||||
ext.log4j_version = '2.17.1'
|
||||
ext.bouncycastle_version = constants.getProperty("bouncycastleVersion")
|
||||
ext.guava_version = constants.getProperty("guavaVersion")
|
||||
ext.caffeine_version = constants.getProperty("caffeineVersion")
|
||||
@ -78,7 +78,7 @@ buildscript {
|
||||
ext.djvm_version = constants.getProperty("djvmVersion")
|
||||
ext.deterministic_rt_version = constants.getProperty('deterministicRtVersion')
|
||||
ext.okhttp_version = '3.14.2'
|
||||
ext.netty_version = '4.1.67.Final'
|
||||
ext.netty_version = '4.1.68.Final'
|
||||
ext.tcnative_version = '2.0.42.Final'
|
||||
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
|
||||
ext.fileupload_version = '1.4'
|
||||
@ -100,7 +100,7 @@ buildscript {
|
||||
ext.hibernate_version = '5.4.3.Final'
|
||||
ext.h2_version = '1.4.199' // Update docs if renamed or removed.
|
||||
ext.rxjava_version = '1.3.8'
|
||||
ext.dokka_version = '0.9.17'
|
||||
ext.dokka_version = '0.10.1'
|
||||
ext.eddsa_version = '0.3.0'
|
||||
ext.dependency_checker_version = '5.2.0'
|
||||
ext.commons_collections_version = '4.3'
|
||||
@ -404,6 +404,7 @@ allprojects {
|
||||
includeGroup 'co.paralleluniverse'
|
||||
includeGroup 'org.crashub'
|
||||
includeGroup 'com.github.bft-smart'
|
||||
includeGroup 'com.github.detro'
|
||||
}
|
||||
}
|
||||
maven {
|
||||
|
@ -21,7 +21,7 @@ jdkClassifier11=jdk11
|
||||
dockerJavaVersion=3.2.5
|
||||
proguardVersion=6.1.1
|
||||
bouncycastleVersion=1.68
|
||||
classgraphVersion=4.8.90
|
||||
classgraphVersion=4.8.135
|
||||
disruptorVersion=3.4.2
|
||||
typesafeConfigVersion=1.3.4
|
||||
jsr305Version=3.0.2
|
||||
|
@ -23,7 +23,10 @@ def javaHome = System.getProperty('java.home')
|
||||
def jarBaseName = "corda-${project.name}".toString()
|
||||
|
||||
configurations {
|
||||
deterministicLibraries.extendsFrom api
|
||||
deterministicLibraries {
|
||||
canBeConsumed = false
|
||||
extendsFrom api
|
||||
}
|
||||
deterministicArtifacts.extendsFrom deterministicLibraries
|
||||
}
|
||||
|
||||
@ -59,7 +62,7 @@ def originalJar = coreJarTask.map { it.outputs.files.singleFile }
|
||||
|
||||
def patchCore = tasks.register('patchCore', Zip) {
|
||||
dependsOn coreJarTask
|
||||
destinationDirectory = file("$buildDir/source-libs")
|
||||
destinationDirectory = layout.buildDirectory.dir('source-libs')
|
||||
metadataCharset 'UTF-8'
|
||||
archiveClassifier = 'transient'
|
||||
archiveExtension = 'jar'
|
||||
@ -169,7 +172,7 @@ def determinise = tasks.register('determinise', ProGuardTask) {
|
||||
def checkDeterminism = tasks.register('checkDeterminism', ProGuardTask)
|
||||
|
||||
def metafix = tasks.register('metafix', MetaFixerTask) {
|
||||
outputDir file("$buildDir/libs")
|
||||
outputDir = layout.buildDirectory.dir('libs')
|
||||
jars determinise
|
||||
suffix ""
|
||||
|
||||
|
@ -55,12 +55,16 @@ abstract class SerializationFactory {
|
||||
* Change the current context inside the block to that supplied.
|
||||
*/
|
||||
fun <T> withCurrentContext(context: SerializationContext?, block: () -> T): T {
|
||||
val priorContext = _currentContext
|
||||
if (context != null) _currentContext = context
|
||||
try {
|
||||
return block()
|
||||
} finally {
|
||||
if (context != null) _currentContext = priorContext
|
||||
return if (context == null) {
|
||||
block()
|
||||
} else {
|
||||
val priorContext = _currentContext
|
||||
_currentContext = context
|
||||
try {
|
||||
block()
|
||||
} finally {
|
||||
_currentContext = priorContext
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,9 @@ plugins {
|
||||
}
|
||||
|
||||
configurations {
|
||||
testData
|
||||
testData {
|
||||
canBeResolved = false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -9,7 +9,12 @@ apply from: "${rootProject.projectDir}/deterministic.gradle"
|
||||
description 'Test utilities for deterministic contract verification'
|
||||
|
||||
configurations {
|
||||
deterministicArtifacts
|
||||
deterministicArtifacts {
|
||||
canBeResolved = false
|
||||
}
|
||||
|
||||
// Compile against the deterministic artifacts to ensure that we use only the deterministic API subset.
|
||||
compileOnly.extendsFrom deterministicArtifacts
|
||||
runtimeArtifacts.extendsFrom api
|
||||
}
|
||||
|
||||
@ -20,8 +25,6 @@ dependencies {
|
||||
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"
|
||||
runtimeOnly "org.junit.vintage:junit-vintage-engine:$junit_vintage_version"
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import net.corda.core.transactions.WireTransaction
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
//TODO the use of deprecated toLedgerTransaction need to be revisited as resolveContractAttachment requires attachments of the transactions which created input states...
|
||||
//TODO ...to check contract version non downgrade rule, curretly dummy Attachment if not fund is used which sets contract version to '1'
|
||||
//TODO ...to check contract version non downgrade rule, currently dummy Attachment if not fund is used which sets contract version to '1'
|
||||
@CordaSerializable
|
||||
class TransactionVerificationRequest(val wtxToVerify: SerializedBytes<WireTransaction>,
|
||||
val dependencies: Array<SerializedBytes<WireTransaction>>,
|
||||
|
@ -55,7 +55,8 @@ class AttachmentsClassLoaderSerializationTests {
|
||||
arrayOf(isolatedId, att1, att2).map { storage.openAttachment(it)!! },
|
||||
testNetworkParameters(),
|
||||
SecureHash.zeroHash,
|
||||
{ attachmentTrustCalculator.calculate(it) }, attachmentsClassLoaderCache = null) { classLoader ->
|
||||
{ attachmentTrustCalculator.calculate(it) }, attachmentsClassLoaderCache = null) { serializationContext ->
|
||||
val classLoader = serializationContext.deserializationClassLoader
|
||||
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, classLoader)
|
||||
val contract = contractClass.getDeclaredConstructor().newInstance() as Contract
|
||||
assertEquals("helloworld", contract.declaredField<Any?>("magicString").value)
|
||||
|
@ -21,6 +21,7 @@ import net.corda.testing.internal.createWireTransaction
|
||||
import net.corda.testing.internal.fakeAttachment
|
||||
import net.corda.coretesting.internal.rigorousMock
|
||||
import net.corda.testing.internal.TestingNamedCacheFactory
|
||||
import org.assertj.core.api.Assertions.fail
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@ -36,6 +37,7 @@ import kotlin.test.assertNotEquals
|
||||
@RunWith(Parameterized::class)
|
||||
class TransactionTests(private val digestService : DigestService) {
|
||||
private companion object {
|
||||
const val ISOLATED_JAR = "isolated-4.0.jar"
|
||||
val DUMMY_KEY_1 = generateKeyPair()
|
||||
val DUMMY_KEY_2 = generateKeyPair()
|
||||
val DUMMY_CASH_ISSUER_KEY = entropyToKeyPair(BigInteger.valueOf(10))
|
||||
@ -200,15 +202,15 @@ class TransactionTests(private val digestService : DigestService) {
|
||||
|
||||
val outputs = listOf(outState)
|
||||
val commands = emptyList<CommandWithParties<CommandData>>()
|
||||
val attachments = listOf(object : AbstractAttachment({
|
||||
AttachmentsClassLoaderTests::class.java.getResource("isolated-4.0.jar").openStream().readBytes()
|
||||
val attachments = listOf(ContractAttachment(object : AbstractAttachment({
|
||||
(AttachmentsClassLoaderTests::class.java.getResource(ISOLATED_JAR) ?: fail("Missing $ISOLATED_JAR")).openStream().readBytes()
|
||||
}, TESTDSL_UPLOADER) {
|
||||
@Suppress("OverridingDeprecatedMember")
|
||||
override val signers: List<Party> = emptyList()
|
||||
override val signerKeys: List<PublicKey> = emptyList()
|
||||
override val size: Int = 1234
|
||||
override val id: SecureHash = SecureHash.zeroHash
|
||||
})
|
||||
}, DummyContract.PROGRAM_ID))
|
||||
val id = digestService.randomHash()
|
||||
val timeWindow: TimeWindow? = null
|
||||
val privacySalt = PrivacySalt(digestService.digestLength)
|
||||
|
@ -12,6 +12,10 @@ description 'Corda core'
|
||||
// required by DJVM and Avian JVM (for running inside the SGX enclave) which only supports Java 8.
|
||||
targetCompatibility = VERSION_1_8
|
||||
|
||||
sourceSets {
|
||||
obfuscator
|
||||
}
|
||||
|
||||
configurations {
|
||||
integrationTestCompile.extendsFrom testCompile
|
||||
integrationTestRuntimeOnly.extendsFrom testRuntimeOnly
|
||||
@ -22,6 +26,9 @@ configurations {
|
||||
|
||||
dependencies {
|
||||
|
||||
obfuscatorImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
|
||||
testImplementation sourceSets.obfuscator.output
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
|
||||
testImplementation "junit:junit:$junit_version"
|
||||
testRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}"
|
||||
@ -110,7 +117,16 @@ configurations {
|
||||
}
|
||||
|
||||
|
||||
test{
|
||||
processTestResources {
|
||||
inputs.files(jar)
|
||||
into("zip") {
|
||||
from(jar) {
|
||||
rename { "core.jar" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
maxParallelForks = (System.env.CORDA_CORE_TESTING_FORKS == null) ? 1 : "$System.env.CORDA_CORE_TESTING_FORKS".toInteger()
|
||||
}
|
||||
|
||||
@ -163,3 +179,10 @@ scanApi {
|
||||
publish {
|
||||
name jar.baseName
|
||||
}
|
||||
|
||||
tasks.register("writeTestResources", JavaExec) {
|
||||
classpath sourceSets.obfuscator.output
|
||||
classpath sourceSets.obfuscator.runtimeClasspath
|
||||
main 'net.corda.core.internal.utilities.TestResourceWriter'
|
||||
args new File(sourceSets.test.resources.srcDirs.first(), "zip").toString()
|
||||
}
|
||||
|
@ -343,7 +343,11 @@ abstract class TransactionVerificationException(val txId: SecureHash, message: S
|
||||
"You will need to manually install the CorDapp to whitelist it for use.")
|
||||
|
||||
@KeepForDJVM
|
||||
class UnsupportedHashTypeException(txId: SecureHash) : TransactionVerificationException(txId, "The transaction Id is defined by an unsupported hash type", null);
|
||||
class UnsupportedHashTypeException(txId: SecureHash) : TransactionVerificationException(txId, "The transaction Id is defined by an unsupported hash type", null)
|
||||
|
||||
@KeepForDJVM
|
||||
class AttachmentTooBigException(txId: SecureHash) : TransactionVerificationException(
|
||||
txId, "The transaction attachments are too large and exceed both max transaction size and the maximum allowed compression ratio", null)
|
||||
|
||||
/*
|
||||
If you add a new class extending [TransactionVerificationException], please add a test in `TransactionVerificationExceptionSerializationTests`
|
||||
|
@ -28,9 +28,7 @@ sealed class DigestAlgorithmFactory {
|
||||
}
|
||||
|
||||
private class CustomAlgorithmFactory(className: String) : DigestAlgorithmFactory() {
|
||||
val constructor: Constructor<out DigestAlgorithm> = javaClass
|
||||
.classLoader
|
||||
.loadClass(className)
|
||||
val constructor: Constructor<out DigestAlgorithm> = Class.forName(className, false, javaClass.classLoader)
|
||||
.asSubclass(DigestAlgorithm::class.java)
|
||||
.getConstructor()
|
||||
override val algorithm: String = constructor.newInstance().algorithm
|
||||
|
@ -11,9 +11,9 @@ import kotlin.concurrent.withLock
|
||||
private val pooledScanMutex = ReentrantLock()
|
||||
|
||||
/**
|
||||
* Use this rather than the built in implementation of [scan] on [ClassGraph]. The built in implementation of [scan] creates
|
||||
* a thread pool every time resulting in too many threads. This one uses a mutex to restrict concurrency.
|
||||
* Use this rather than the built-in implementation of [ClassGraph.scan]. The built-in implementation creates
|
||||
* a thread pool every time, resulting in too many threads. This one uses a mutex to restrict concurrency.
|
||||
*/
|
||||
fun ClassGraph.pooledScan(): ScanResult {
|
||||
return pooledScanMutex.withLock { this@pooledScan.scan() }
|
||||
return pooledScanMutex.withLock(::scan)
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import net.corda.core.serialization.internal.AttachmentURLStreamHandlerFactory.a
|
||||
fun <T: Any> createInstancesOfClassesImplementing(classloader: ClassLoader, clazz: Class<T>,
|
||||
classVersionRange: IntRange? = null): Set<T> {
|
||||
return getNamesOfClassesImplementing(classloader, clazz, classVersionRange)
|
||||
.map { classloader.loadClass(it).asSubclass(clazz) }
|
||||
.map { Class.forName(it, false, classloader).asSubclass(clazz) }
|
||||
.mapTo(LinkedHashSet()) { it.kotlin.objectOrNewInstance() }
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ typealias Version = Int
|
||||
* Attention: this value affects consensus, so it requires a minimum platform version bump in order to be changed.
|
||||
*/
|
||||
const val MAX_NUMBER_OF_KEYS_IN_SIGNATURE_CONSTRAINT = 20
|
||||
private const val DJVM_SANDBOX_PREFIX = "sandbox."
|
||||
|
||||
private val log = loggerFor<AttachmentConstraint>()
|
||||
|
||||
@ -29,10 +30,14 @@ val Attachment.contractVersion: Version get() = if (this is ContractAttachment)
|
||||
val ContractState.requiredContractClassName: String? get() {
|
||||
val annotation = javaClass.getAnnotation(BelongsToContract::class.java)
|
||||
if (annotation != null) {
|
||||
return annotation.value.java.typeName
|
||||
return annotation.value.java.typeName.removePrefix(DJVM_SANDBOX_PREFIX)
|
||||
}
|
||||
val enclosingClass = javaClass.enclosingClass ?: return null
|
||||
return if (Contract::class.java.isAssignableFrom(enclosingClass)) enclosingClass.typeName else null
|
||||
return if (Contract::class.java.isAssignableFrom(enclosingClass)) {
|
||||
enclosingClass.typeName.removePrefix(DJVM_SANDBOX_PREFIX)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,7 +56,9 @@ import java.security.cert.TrustAnchor
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Duration
|
||||
import java.time.temporal.Temporal
|
||||
import java.util.*
|
||||
import java.util.Collections
|
||||
import java.util.PrimitiveIterator
|
||||
import java.util.Spliterator
|
||||
import java.util.Spliterator.DISTINCT
|
||||
import java.util.Spliterator.IMMUTABLE
|
||||
import java.util.Spliterator.NONNULL
|
||||
@ -64,6 +66,7 @@ import java.util.Spliterator.ORDERED
|
||||
import java.util.Spliterator.SIZED
|
||||
import java.util.Spliterator.SORTED
|
||||
import java.util.Spliterator.SUBSIZED
|
||||
import java.util.Spliterators
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.stream.Collectors
|
||||
|
@ -16,5 +16,6 @@ object PlatformVersionSwitches {
|
||||
const val LIMIT_KEYS_IN_SIGNATURE_CONSTRAINTS = 5
|
||||
const val BATCH_DOWNLOAD_COUNTERPARTY_BACKCHAIN = 6
|
||||
const val ENABLE_P2P_COMPRESSION = 7
|
||||
const val RESTRICTED_DATABASE_OPERATIONS = 7
|
||||
const val CERTIFICATE_ROTATION = 9
|
||||
}
|
@ -54,7 +54,7 @@ fun combinedHash(components: Iterable<SecureHash>, digestService: DigestService)
|
||||
components.forEach {
|
||||
stream.write(it.bytes)
|
||||
}
|
||||
return digestService.hash(stream.toByteArray());
|
||||
return digestService.hash(stream.toByteArray())
|
||||
}
|
||||
|
||||
/**
|
||||
@ -114,14 +114,14 @@ fun deserialiseCommands(
|
||||
componentGroups: List<ComponentGroup>,
|
||||
forceDeserialize: Boolean = false,
|
||||
factory: SerializationFactory = SerializationFactory.defaultFactory,
|
||||
@Suppress("UNUSED_PARAMETER") context: SerializationContext = factory.defaultContext,
|
||||
context: SerializationContext = factory.defaultContext,
|
||||
digestService: DigestService = DigestService.sha2_256
|
||||
): List<Command<*>> {
|
||||
// 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: List<List<PublicKey>> = uncheckedCast(deserialiseComponentGroup(componentGroups, List::class, ComponentGroupEnum.SIGNERS_GROUP, forceDeserialize))
|
||||
val commandDataList: List<CommandData> = deserialiseComponentGroup(componentGroups, CommandData::class, ComponentGroupEnum.COMMANDS_GROUP, forceDeserialize)
|
||||
val signersList: List<List<PublicKey>> = uncheckedCast(deserialiseComponentGroup(componentGroups, List::class, ComponentGroupEnum.SIGNERS_GROUP, forceDeserialize, factory, context))
|
||||
val commandDataList: List<CommandData> = deserialiseComponentGroup(componentGroups, CommandData::class, ComponentGroupEnum.COMMANDS_GROUP, forceDeserialize, factory, context)
|
||||
val group = componentGroups.firstOrNull { it.groupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal }
|
||||
return if (group is FilteredComponentGroup) {
|
||||
check(commandDataList.size <= signersList.size) {
|
||||
@ -154,8 +154,9 @@ fun createComponentGroups(inputs: List<StateRef>,
|
||||
timeWindow: TimeWindow?,
|
||||
references: List<StateRef>,
|
||||
networkParametersHash: SecureHash?): List<ComponentGroup> {
|
||||
val serializationContext = SerializationFactory.defaultFactory.defaultContext
|
||||
val serialize = { value: Any, _: Int -> value.serialize(context = serializationContext) }
|
||||
val serializationFactory = SerializationFactory.defaultFactory
|
||||
val serializationContext = serializationFactory.defaultContext
|
||||
val serialize = { value: Any, _: Int -> value.serialize(serializationFactory, serializationContext) }
|
||||
val componentGroupMap: MutableList<ComponentGroup> = mutableListOf()
|
||||
if (inputs.isNotEmpty()) componentGroupMap.add(ComponentGroup(ComponentGroupEnum.INPUTS_GROUP.ordinal, inputs.lazyMapped(serialize)))
|
||||
if (references.isNotEmpty()) componentGroupMap.add(ComponentGroup(ComponentGroupEnum.REFERENCES_GROUP.ordinal, references.lazyMapped(serialize)))
|
||||
@ -178,7 +179,11 @@ fun createComponentGroups(inputs: List<StateRef>,
|
||||
*/
|
||||
@KeepForDJVM
|
||||
data class SerializedStateAndRef(val serializedState: SerializedBytes<TransactionState<ContractState>>, val ref: StateRef) {
|
||||
fun toStateAndRef(): StateAndRef<ContractState> = StateAndRef(serializedState.deserialize(), ref)
|
||||
fun toStateAndRef(factory: SerializationFactory, context: SerializationContext) = StateAndRef(serializedState.deserialize(factory, context), ref)
|
||||
fun toStateAndRef(): StateAndRef<ContractState> {
|
||||
val factory = SerializationFactory.defaultFactory
|
||||
return toStateAndRef(factory, factory.defaultContext)
|
||||
}
|
||||
}
|
||||
|
||||
/** Check that network parameters hash on this transaction is the current hash for the network. */
|
||||
|
@ -3,14 +3,40 @@ package net.corda.core.internal
|
||||
import net.corda.core.DeleteForDJVM
|
||||
import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.ContractAttachment
|
||||
import net.corda.core.contracts.ContractClassName
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.HashAttachmentConstraint
|
||||
import net.corda.core.contracts.SignatureAttachmentConstraint
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.contracts.TransactionVerificationException
|
||||
import net.corda.core.contracts.TransactionVerificationException.ConflictingAttachmentsRejection
|
||||
import net.corda.core.contracts.TransactionVerificationException.ConstraintPropagationRejection
|
||||
import net.corda.core.contracts.TransactionVerificationException.ContractCreationError
|
||||
import net.corda.core.contracts.TransactionVerificationException.ContractRejection
|
||||
import net.corda.core.contracts.TransactionVerificationException.ContractConstraintRejection
|
||||
import net.corda.core.contracts.TransactionVerificationException.Direction
|
||||
import net.corda.core.contracts.TransactionVerificationException.DuplicateAttachmentsRejection
|
||||
import net.corda.core.contracts.TransactionVerificationException.InvalidConstraintRejection
|
||||
import net.corda.core.contracts.TransactionVerificationException.MissingAttachmentRejection
|
||||
import net.corda.core.contracts.TransactionVerificationException.NotaryChangeInWrongTransactionType
|
||||
import net.corda.core.contracts.TransactionVerificationException.TransactionContractConflictException
|
||||
import net.corda.core.contracts.TransactionVerificationException.TransactionDuplicateEncumbranceException
|
||||
import net.corda.core.contracts.TransactionVerificationException.TransactionMissingEncumbranceException
|
||||
import net.corda.core.contracts.TransactionVerificationException.TransactionNonMatchingEncumbranceException
|
||||
import net.corda.core.contracts.TransactionVerificationException.TransactionNotaryMismatchEncumbranceException
|
||||
import net.corda.core.contracts.TransactionVerificationException.TransactionRequiredContractUnspecifiedException
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.internal.rules.StateContractValidationEnforcementRule
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import java.util.function.Function
|
||||
import java.util.function.Supplier
|
||||
|
||||
@DeleteForDJVM
|
||||
interface TransactionVerifierServiceInternal {
|
||||
@ -22,16 +48,54 @@ interface TransactionVerifierServiceInternal {
|
||||
*/
|
||||
fun LedgerTransaction.prepareVerify(attachments: List<Attachment>) = internalPrepareVerify(attachments)
|
||||
|
||||
interface Verifier {
|
||||
|
||||
/**
|
||||
* Placeholder function for the verification logic.
|
||||
*/
|
||||
fun verify()
|
||||
}
|
||||
|
||||
// This class allows us unit-test transaction verification more easily.
|
||||
abstract class AbstractVerifier(
|
||||
protected val ltx: LedgerTransaction,
|
||||
protected val transactionClassLoader: ClassLoader
|
||||
) : Verifier {
|
||||
protected abstract val transaction: Supplier<LedgerTransaction>
|
||||
|
||||
protected companion object {
|
||||
@JvmField
|
||||
val logger = loggerFor<Verifier>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the transaction is internally consistent, and then check that it is
|
||||
* contract-valid by running verify() for each input and output state contract.
|
||||
* If any contract fails to verify, the whole transaction is considered to be invalid.
|
||||
*
|
||||
* Note: Reference states are not verified.
|
||||
*/
|
||||
final override fun verify() {
|
||||
try {
|
||||
TransactionVerifier(transactionClassLoader).apply(transaction)
|
||||
} catch (e: TransactionVerificationException) {
|
||||
logger.error("Error validating transaction ${ltx.id}.", e.cause)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Because we create a separate [LedgerTransaction] onto which we need to perform verification, it becomes important we don't verify the
|
||||
* wrong object instance. This class helps avoid that.
|
||||
*/
|
||||
abstract class Verifier(val ltx: LedgerTransaction, protected val transactionClassLoader: ClassLoader) {
|
||||
private val inputStates: List<TransactionState<*>> = ltx.inputs.map { it.state }
|
||||
private val allStates: List<TransactionState<*>> = inputStates + ltx.references.map { it.state } + ltx.outputs
|
||||
@KeepForDJVM
|
||||
private class Validator(private val ltx: LedgerTransaction, private val transactionClassLoader: ClassLoader) {
|
||||
private val inputStates: List<TransactionState<*>> = ltx.inputs.map(StateAndRef<ContractState>::state)
|
||||
private val allStates: List<TransactionState<*>> = inputStates + ltx.references.map(StateAndRef<ContractState>::state) + ltx.outputs
|
||||
|
||||
companion object {
|
||||
val logger = contextLogger()
|
||||
private companion object {
|
||||
private val logger = loggerFor<Validator>()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -39,9 +103,9 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla
|
||||
*
|
||||
* It is a critical piece of the security of the platform.
|
||||
*
|
||||
* @throws TransactionVerificationException
|
||||
* @throws net.corda.core.contracts.TransactionVerificationException
|
||||
*/
|
||||
fun verify() {
|
||||
fun validate() {
|
||||
// checkNoNotaryChange and checkEncumbrancesValid are called here, and not in the c'tor, as they need access to the "outputs"
|
||||
// list, the contents of which need to be deserialized under the correct classloader.
|
||||
checkNoNotaryChange()
|
||||
@ -68,8 +132,7 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla
|
||||
// 4. Check that the [TransactionState] objects are correctly formed.
|
||||
validateStatesAgainstContract()
|
||||
|
||||
// 5. Final step is to run the contract code. After the first 4 steps we are now sure that we are running the correct code.
|
||||
verifyContracts()
|
||||
// 5. Final step will be to run the contract code.
|
||||
}
|
||||
|
||||
private fun checkTransactionWithTimeWindowIsNotarised() {
|
||||
@ -81,11 +144,12 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla
|
||||
* It makes sure there is one and only one.
|
||||
* This is an important piece of the security of transactions.
|
||||
*/
|
||||
@Suppress("ThrowsCount")
|
||||
private fun getUniqueContractAttachmentsByContract(): Map<ContractClassName, ContractAttachment> {
|
||||
val contractClasses = allStates.map { it.contract }.toSet()
|
||||
val contractClasses = allStates.mapTo(LinkedHashSet(), TransactionState<*>::contract)
|
||||
|
||||
// Check that there are no duplicate attachments added.
|
||||
if (ltx.attachments.size != ltx.attachments.toSet().size) throw TransactionVerificationException.DuplicateAttachmentsRejection(ltx.id, ltx.attachments.groupBy { it }.filterValues { it.size > 1 }.keys.first())
|
||||
if (ltx.attachments.size != ltx.attachments.toSet().size) throw DuplicateAttachmentsRejection(ltx.id, ltx.attachments.groupBy { it }.filterValues { it.size > 1 }.keys.first())
|
||||
|
||||
// For each attachment this finds all the relevant state contracts that it provides.
|
||||
// And then maps them to the attachment.
|
||||
@ -103,12 +167,12 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla
|
||||
.groupBy { it.first } // Group by contract.
|
||||
.filter { (_, attachments) -> attachments.size > 1 } // And only keep contracts that are in multiple attachments. It's guaranteed that attachments were unique by a previous check.
|
||||
.keys.firstOrNull() // keep the first one - if any - to throw a meaningful exception.
|
||||
if (contractWithMultipleAttachments != null) throw TransactionVerificationException.ConflictingAttachmentsRejection(ltx.id, contractWithMultipleAttachments)
|
||||
if (contractWithMultipleAttachments != null) throw ConflictingAttachmentsRejection(ltx.id, contractWithMultipleAttachments)
|
||||
|
||||
val result = contractAttachmentsPerContract.toMap()
|
||||
|
||||
// Check that there is an attachment for each contract.
|
||||
if (result.keys != contractClasses) throw TransactionVerificationException.MissingAttachmentRejection(ltx.id, contractClasses.minus(result.keys).first())
|
||||
if (result.keys != contractClasses) throw MissingAttachmentRejection(ltx.id, contractClasses.minus(result.keys).first())
|
||||
|
||||
return result
|
||||
}
|
||||
@ -124,7 +188,7 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla
|
||||
if (ltx.notary != null && (ltx.inputs.isNotEmpty() || ltx.references.isNotEmpty())) {
|
||||
ltx.outputs.forEach {
|
||||
if (it.notary != ltx.notary) {
|
||||
throw TransactionVerificationException.NotaryChangeInWrongTransactionType(ltx.id, ltx.notary, it.notary)
|
||||
throw NotaryChangeInWrongTransactionType(ltx.id, ltx.notary, it.notary)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -156,10 +220,10 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla
|
||||
it.ref.txhash == ref.txhash && it.ref.index == state.encumbrance
|
||||
}
|
||||
if (!encumbranceStateExists) {
|
||||
throw TransactionVerificationException.TransactionMissingEncumbranceException(
|
||||
throw TransactionMissingEncumbranceException(
|
||||
ltx.id,
|
||||
state.encumbrance!!,
|
||||
TransactionVerificationException.Direction.INPUT
|
||||
Direction.INPUT
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -185,6 +249,7 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla
|
||||
// b -> c and c -> b
|
||||
// c -> a b -> a
|
||||
// and form a full cycle, meaning that the bi-directionality property is satisfied.
|
||||
@Suppress("ThrowsCount")
|
||||
private fun checkBidirectionalOutputEncumbrances(statesAndEncumbrance: List<Pair<Int, Int>>) {
|
||||
// [Set] of "from" (encumbered states).
|
||||
val encumberedSet = mutableSetOf<Int>()
|
||||
@ -194,15 +259,15 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla
|
||||
statesAndEncumbrance.forEach { (statePosition, encumbrance) ->
|
||||
// Check it does not refer to itself.
|
||||
if (statePosition == encumbrance || encumbrance >= ltx.outputs.size) {
|
||||
throw TransactionVerificationException.TransactionMissingEncumbranceException(
|
||||
throw TransactionMissingEncumbranceException(
|
||||
ltx.id,
|
||||
encumbrance,
|
||||
TransactionVerificationException.Direction.OUTPUT
|
||||
Direction.OUTPUT
|
||||
)
|
||||
} else {
|
||||
encumberedSet.add(statePosition) // Guaranteed to have unique elements.
|
||||
if (!encumbranceSet.add(encumbrance)) {
|
||||
throw TransactionVerificationException.TransactionDuplicateEncumbranceException(ltx.id, encumbrance)
|
||||
throw TransactionDuplicateEncumbranceException(ltx.id, encumbrance)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -211,7 +276,7 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla
|
||||
val symmetricDifference = (encumberedSet union encumbranceSet).subtract(encumberedSet intersect encumbranceSet)
|
||||
if (symmetricDifference.isNotEmpty()) {
|
||||
// At least one encumbered state is not in the [encumbranceSet] and vice versa.
|
||||
throw TransactionVerificationException.TransactionNonMatchingEncumbranceException(ltx.id, symmetricDifference)
|
||||
throw TransactionNonMatchingEncumbranceException(ltx.id, symmetricDifference)
|
||||
}
|
||||
}
|
||||
|
||||
@ -235,7 +300,7 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla
|
||||
if (indicesAlreadyChecked.add(index)) {
|
||||
val encumbranceIndex = ltx.outputs[index].encumbrance!!
|
||||
if (ltx.outputs[index].notary != ltx.outputs[encumbranceIndex].notary) {
|
||||
throw TransactionVerificationException.TransactionNotaryMismatchEncumbranceException(
|
||||
throw TransactionNotaryMismatchEncumbranceException(
|
||||
ltx.id,
|
||||
index,
|
||||
encumbranceIndex,
|
||||
@ -263,7 +328,7 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla
|
||||
val shouldEnforce = StateContractValidationEnforcementRule.shouldEnforce(state.data)
|
||||
|
||||
val requiredContractClassName = state.data.requiredContractClassName
|
||||
?: if (shouldEnforce) throw TransactionVerificationException.TransactionRequiredContractUnspecifiedException(ltx.id, state) else return
|
||||
?: if (shouldEnforce) throw TransactionRequiredContractUnspecifiedException(ltx.id, state) else return
|
||||
|
||||
if (state.contract != requiredContractClassName)
|
||||
if (shouldEnforce) {
|
||||
@ -281,6 +346,7 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla
|
||||
* - Constraints should be one of the valid supported ones.
|
||||
* - Constraints should propagate correctly if not marked otherwise (in that case it is the responsibility of the contract to ensure that the output states are created properly).
|
||||
*/
|
||||
@Suppress("NestedBlockDepth")
|
||||
private fun verifyConstraintsValidity(contractAttachmentsByContract: Map<ContractClassName, ContractAttachment>) {
|
||||
|
||||
// First check that the constraints are valid.
|
||||
@ -310,7 +376,7 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla
|
||||
outputConstraints.forEach { outputConstraint ->
|
||||
inputConstraints.forEach { inputConstraint ->
|
||||
if (!(outputConstraint.canBeTransitionedFrom(inputConstraint, contractAttachment))) {
|
||||
throw TransactionVerificationException.ConstraintPropagationRejection(
|
||||
throw ConstraintPropagationRejection(
|
||||
ltx.id,
|
||||
contractClassName,
|
||||
inputConstraint,
|
||||
@ -331,7 +397,7 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla
|
||||
@Suppress("NestedBlockDepth", "MagicNumber")
|
||||
private fun verifyConstraints(contractAttachmentsByContract: Map<ContractClassName, ContractAttachment>) {
|
||||
// For each contract/constraint pair check that the relevant attachment is valid.
|
||||
allStates.map { it.contract to it.constraint }.toSet().forEach { (contract, constraint) ->
|
||||
allStates.mapTo(LinkedHashSet()) { it.contract to it.constraint }.forEach { (contract, constraint) ->
|
||||
if (constraint is SignatureAttachmentConstraint) {
|
||||
/**
|
||||
* Support for signature constraints has been added on
|
||||
@ -346,9 +412,9 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla
|
||||
"Signature constraints"
|
||||
)
|
||||
val constraintKey = constraint.key
|
||||
if (ltx.networkParameters?.minimumPlatformVersion ?: 1 >= PlatformVersionSwitches.LIMIT_KEYS_IN_SIGNATURE_CONSTRAINTS) {
|
||||
if ((ltx.networkParameters?.minimumPlatformVersion ?: 1) >= PlatformVersionSwitches.LIMIT_KEYS_IN_SIGNATURE_CONSTRAINTS) {
|
||||
if (constraintKey is CompositeKey && constraintKey.leafKeys.size > MAX_NUMBER_OF_KEYS_IN_SIGNATURE_CONSTRAINT) {
|
||||
throw TransactionVerificationException.InvalidConstraintRejection(ltx.id, contract,
|
||||
throw InvalidConstraintRejection(ltx.id, contract,
|
||||
"Signature constraint contains composite key with ${constraintKey.leafKeys.size} leaf keys, " +
|
||||
"which is more than the maximum allowed number of keys " +
|
||||
"($MAX_NUMBER_OF_KEYS_IN_SIGNATURE_CONSTRAINT).")
|
||||
@ -364,39 +430,18 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla
|
||||
if (HashAttachmentConstraint.disableHashConstraints && constraint is HashAttachmentConstraint)
|
||||
logger.warnOnce("Skipping hash constraints verification.")
|
||||
else if (!constraint.isSatisfiedBy(constraintAttachment))
|
||||
throw TransactionVerificationException.ContractConstraintRejection(ltx.id, contract)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder function for the contract verification logic.
|
||||
*/
|
||||
abstract fun verifyContracts()
|
||||
}
|
||||
|
||||
class BasicVerifier(ltx: LedgerTransaction, transactionClassLoader: ClassLoader) : Verifier(ltx, transactionClassLoader) {
|
||||
/**
|
||||
* Check the transaction is contract-valid by running the verify() for each input and output state contract.
|
||||
* If any contract fails to verify, the whole transaction is considered to be invalid.
|
||||
*
|
||||
* Note: Reference states are not verified.
|
||||
*/
|
||||
override fun verifyContracts() {
|
||||
try {
|
||||
ContractVerifier(transactionClassLoader).apply(ltx)
|
||||
} catch (e: TransactionVerificationException.ContractRejection) {
|
||||
logger.error("Error validating transaction ${ltx.id}.", e.cause)
|
||||
throw e
|
||||
throw ContractConstraintRejection(ltx.id, contract)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify all of the contracts on the given [LedgerTransaction].
|
||||
* Verify the given [LedgerTransaction]. This includes validating
|
||||
* its contents, as well as executing all of its smart contracts.
|
||||
*/
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
@KeepForDJVM
|
||||
class ContractVerifier(private val transactionClassLoader: ClassLoader) : Function<LedgerTransaction, Unit> {
|
||||
class TransactionVerifier(private val transactionClassLoader: ClassLoader) : Function<Supplier<LedgerTransaction>, Unit> {
|
||||
// This constructor is used inside the DJVM's sandbox.
|
||||
@Suppress("unused")
|
||||
constructor() : this(ClassLoader.getSystemClassLoader())
|
||||
@ -406,34 +451,62 @@ class ContractVerifier(private val transactionClassLoader: ClassLoader) : Functi
|
||||
return try {
|
||||
Class.forName(contractClassName, false, transactionClassLoader).asSubclass(Contract::class.java)
|
||||
} catch (e: Exception) {
|
||||
throw TransactionVerificationException.ContractCreationError(id, contractClassName, e)
|
||||
throw ContractCreationError(id, contractClassName, e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun apply(ltx: LedgerTransaction) {
|
||||
val contractClassNames = (ltx.inputs.map(StateAndRef<ContractState>::state) + ltx.outputs)
|
||||
private fun generateContracts(ltx: LedgerTransaction): List<Contract> {
|
||||
return (ltx.inputs.map(StateAndRef<ContractState>::state) + ltx.outputs)
|
||||
.mapTo(LinkedHashSet(), TransactionState<*>::contract)
|
||||
|
||||
contractClassNames.associateBy(
|
||||
{ it }, { createContractClass(ltx.id, it) }
|
||||
).map { (contractClassName, contractClass) ->
|
||||
try {
|
||||
/**
|
||||
* This function must execute with the DJVM's sandbox, which does not
|
||||
* permit user code to invoke [java.lang.Class.getDeclaredConstructor].
|
||||
*
|
||||
* [Class.newInstance] is deprecated as of Java 9.
|
||||
*/
|
||||
@Suppress("deprecation")
|
||||
contractClass.newInstance()
|
||||
} catch (e: Exception) {
|
||||
throw TransactionVerificationException.ContractCreationError(ltx.id, contractClassName, e)
|
||||
.map { contractClassName ->
|
||||
createContractClass(ltx.id, contractClassName)
|
||||
}.map { contractClass ->
|
||||
try {
|
||||
/**
|
||||
* This function must execute within the DJVM's sandbox, which does not
|
||||
* permit user code to invoke [java.lang.reflect.Constructor.newInstance].
|
||||
* (This would be fixable now, provided the constructor is public.)
|
||||
*
|
||||
* [Class.newInstance] is deprecated as of Java 9.
|
||||
*/
|
||||
@Suppress("deprecation")
|
||||
contractClass.newInstance()
|
||||
} catch (e: Exception) {
|
||||
throw ContractCreationError(ltx.id, contractClass.name, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateTransaction(ltx: LedgerTransaction) {
|
||||
Validator(ltx, transactionClassLoader).validate()
|
||||
}
|
||||
|
||||
override fun apply(transactionFactory: Supplier<LedgerTransaction>) {
|
||||
var firstLtx: LedgerTransaction? = null
|
||||
|
||||
transactionFactory.get().let { ltx ->
|
||||
firstLtx = ltx
|
||||
|
||||
/**
|
||||
* Check that this transaction is correctly formed.
|
||||
* We only need to run these checks once.
|
||||
*/
|
||||
validateTransaction(ltx)
|
||||
|
||||
/**
|
||||
* Generate the list of unique contracts
|
||||
* within this transaction.
|
||||
*/
|
||||
generateContracts(ltx)
|
||||
}.forEach { contract ->
|
||||
val ltx = firstLtx ?: transactionFactory.get()
|
||||
firstLtx = null
|
||||
try {
|
||||
// Final step is to run the contract code. Having validated the
|
||||
// transaction, we are now sure that we are running the correct code.
|
||||
contract.verify(ltx)
|
||||
} catch (e: Exception) {
|
||||
throw TransactionVerificationException.ContractRejection(ltx.id, contract, e)
|
||||
throw ContractRejection(ltx.id, contract, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.crypto.toStringShort
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.IdempotentFlow
|
||||
import net.corda.core.internal.PlatformVersionSwitches
|
||||
import net.corda.core.internal.checkParameterHash
|
||||
import net.corda.core.utilities.seconds
|
||||
@ -33,7 +32,7 @@ abstract class NotaryServiceFlow(
|
||||
val otherSideSession: FlowSession,
|
||||
val service: SinglePartyNotaryService,
|
||||
private val etaThreshold: Duration
|
||||
) : FlowLogic<Void?>(), IdempotentFlow {
|
||||
) : FlowLogic<Void?>() {
|
||||
companion object {
|
||||
// TODO: Determine an appropriate limit and also enforce in the network parameters and the transaction builder.
|
||||
private const val maxAllowedInputsAndReferences = 10_000
|
||||
|
@ -0,0 +1,63 @@
|
||||
package net.corda.core.internal.utilities
|
||||
|
||||
import java.io.FilterInputStream
|
||||
import java.io.InputStream
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
object ZipBombDetector {
|
||||
|
||||
private class CounterInputStream(source : InputStream) : FilterInputStream(source) {
|
||||
private var byteCount : Long = 0
|
||||
|
||||
val count : Long
|
||||
get() = byteCount
|
||||
|
||||
override fun read(): Int {
|
||||
return super.read().also { byte ->
|
||||
if(byte >= 0) byteCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(b: ByteArray): Int {
|
||||
return super.read(b).also { bytesRead ->
|
||||
if(bytesRead > 0) byteCount += bytesRead
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(b: ByteArray, off: Int, len: Int): Int {
|
||||
return super.read(b, off, len).also { bytesRead ->
|
||||
if(bytesRead > 0) byteCount += bytesRead
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a zip file is a potential malicious zip bomb
|
||||
* @param source the zip archive file content
|
||||
* @param maxUncompressedSize the maximum allowable uncompressed archive size
|
||||
* @param maxCompressionRatio the maximum allowable compression ratio
|
||||
* @return true if the zip file total uncompressed size exceeds [maxUncompressedSize] and the
|
||||
* average entry compression ratio is larger than [maxCompressionRatio], false otherwise
|
||||
*/
|
||||
@Suppress("NestedBlockDepth")
|
||||
fun scanZip(source : InputStream, maxUncompressedSize : Long, maxCompressionRatio : Float = 10.0f) : Boolean {
|
||||
val counterInputStream = CounterInputStream(source)
|
||||
var uncompressedByteCount : Long = 0
|
||||
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||
ZipInputStream(counterInputStream).use { zipInputStream ->
|
||||
while(true) {
|
||||
zipInputStream.nextEntry ?: break
|
||||
while(true) {
|
||||
val read = zipInputStream.read(buffer)
|
||||
if(read <= 0) break
|
||||
uncompressedByteCount += read
|
||||
if(uncompressedByteCount > maxUncompressedSize &&
|
||||
uncompressedByteCount.toFloat() / counterInputStream.count.toFloat() > maxCompressionRatio) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
@ -13,6 +13,8 @@ import net.corda.core.utilities.days
|
||||
import java.security.PublicKey
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.Collections.unmodifiableList
|
||||
import java.util.Collections.unmodifiableMap
|
||||
|
||||
// DOCSTART 1
|
||||
/**
|
||||
@ -166,6 +168,38 @@ data class NetworkParameters(
|
||||
epoch=$epoch
|
||||
}"""
|
||||
}
|
||||
|
||||
fun toImmutable(): NetworkParameters {
|
||||
return NetworkParameters(
|
||||
minimumPlatformVersion = minimumPlatformVersion,
|
||||
notaries = unmodifiable(notaries),
|
||||
maxMessageSize = maxMessageSize,
|
||||
maxTransactionSize = maxTransactionSize,
|
||||
modifiedTime = modifiedTime,
|
||||
epoch = epoch,
|
||||
whitelistedContractImplementations = unmodifiable(whitelistedContractImplementations) { entry ->
|
||||
unmodifiableList(entry.value)
|
||||
},
|
||||
eventHorizon = eventHorizon,
|
||||
packageOwnership = unmodifiable(packageOwnership)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> unmodifiable(list: List<T>): List<T> {
|
||||
return if (list.isEmpty()) {
|
||||
emptyList()
|
||||
} else {
|
||||
unmodifiableList(list)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <K, V> unmodifiable(map: Map<K, V>, transform: (Map.Entry<K, V>) -> V = Map.Entry<K, V>::value): Map<K, V> {
|
||||
return if (map.isEmpty()) {
|
||||
emptyMap()
|
||||
} else {
|
||||
unmodifiableMap(map.mapValues(transform))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -13,6 +13,10 @@ import net.corda.core.utilities.sequence
|
||||
import java.io.NotSerializableException
|
||||
import java.sql.Blob
|
||||
|
||||
const val DESERIALIZATION_CACHE_PROPERTY = "DESERIALIZATION_CACHE"
|
||||
const val AMQP_ENVELOPE_CACHE_PROPERTY = "AMQP_ENVELOPE_CACHE"
|
||||
const val AMQP_ENVELOPE_CACHE_INITIAL_CAPACITY = 256
|
||||
|
||||
data class ObjectWithCompatibleContext<out T : Any>(val obj: T, val context: SerializationContext)
|
||||
|
||||
/**
|
||||
@ -65,12 +69,16 @@ abstract class SerializationFactory {
|
||||
* Change the current context inside the block to that supplied.
|
||||
*/
|
||||
fun <T> withCurrentContext(context: SerializationContext?, block: () -> T): T {
|
||||
val priorContext = _currentContext.get()
|
||||
if (context != null) _currentContext.set(context)
|
||||
try {
|
||||
return block()
|
||||
} finally {
|
||||
if (context != null) _currentContext.set(priorContext)
|
||||
return if (context == null) {
|
||||
block()
|
||||
} else {
|
||||
val priorContext = _currentContext.get()
|
||||
_currentContext.set(context)
|
||||
try {
|
||||
block()
|
||||
} finally {
|
||||
_currentContext.set(priorContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,21 +9,42 @@ import net.corda.core.contracts.TransactionVerificationException
|
||||
import net.corda.core.contracts.TransactionVerificationException.OverlappingAttachmentsException
|
||||
import net.corda.core.contracts.TransactionVerificationException.PackageOwnershipException
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.internal.JDK1_2_CLASS_FILE_FORMAT_MAJOR_VERSION
|
||||
import net.corda.core.internal.JDK8_CLASS_FILE_FORMAT_MAJOR_VERSION
|
||||
import net.corda.core.internal.JarSignatureCollector
|
||||
import net.corda.core.internal.NamedCacheFactory
|
||||
import net.corda.core.internal.PlatformVersionSwitches
|
||||
import net.corda.core.internal.VisibleForTesting
|
||||
import net.corda.core.internal.cordapp.targetPlatformVersion
|
||||
import net.corda.core.internal.createInstancesOfClassesImplementing
|
||||
import net.corda.core.internal.createSimpleCache
|
||||
import net.corda.core.internal.toSynchronised
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.serialization.AMQP_ENVELOPE_CACHE_INITIAL_CAPACITY
|
||||
import net.corda.core.serialization.AMQP_ENVELOPE_CACHE_PROPERTY
|
||||
import net.corda.core.serialization.DESERIALIZATION_CACHE_PROPERTY
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import net.corda.core.serialization.SerializationFactory
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.serialization.internal.AttachmentURLStreamHandlerFactory.toUrl
|
||||
import net.corda.core.serialization.withWhitelist
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.debug
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.lang.ref.WeakReference
|
||||
import java.net.*
|
||||
import java.net.URL
|
||||
import java.net.URLClassLoader
|
||||
import java.net.URLConnection
|
||||
import java.net.URLStreamHandler
|
||||
import java.net.URLStreamHandlerFactory
|
||||
import java.security.MessageDigest
|
||||
import java.security.Permission
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
import java.util.ServiceLoader
|
||||
import java.util.WeakHashMap
|
||||
import java.util.function.Function
|
||||
|
||||
/**
|
||||
@ -51,12 +72,15 @@ class AttachmentsClassLoader(attachments: List<Attachment>,
|
||||
init {
|
||||
// Apply our own URLStreamHandlerFactory to resolve attachments
|
||||
setOrDecorateURLStreamHandlerFactory()
|
||||
|
||||
// Allow AttachmentsClassLoader to be used concurrently.
|
||||
registerAsParallelCapable()
|
||||
}
|
||||
|
||||
// Jolokia and Json-simple are dependencies that were bundled by mistake within contract jars.
|
||||
// In the AttachmentsClassLoader we just block any class in those 2 packages.
|
||||
private val ignoreDirectories = listOf("org/jolokia/", "org/json/simple/")
|
||||
private val ignorePackages = ignoreDirectories.map { it.replace("/", ".") }
|
||||
private val ignorePackages = ignoreDirectories.map { it.replace('/', '.') }
|
||||
|
||||
/**
|
||||
* Apply our custom factory either directly, if `URL.setURLStreamHandlerFactory` has not been called yet,
|
||||
@ -128,6 +152,20 @@ class AttachmentsClassLoader(attachments: List<Attachment>,
|
||||
checkAttachments(attachments)
|
||||
}
|
||||
|
||||
private class AttachmentHashContext(
|
||||
val txId: SecureHash,
|
||||
val buffer: ByteArray = ByteArray(DEFAULT_BUFFER_SIZE))
|
||||
|
||||
private fun hash(inputStream : InputStream, ctx : AttachmentHashContext) : SecureHash.SHA256 {
|
||||
val md = MessageDigest.getInstance(SecureHash.SHA2_256)
|
||||
while(true) {
|
||||
val read = inputStream.read(ctx.buffer)
|
||||
if(read <= 0) break
|
||||
md.update(ctx.buffer, 0, read)
|
||||
}
|
||||
return SecureHash.SHA256(md.digest())
|
||||
}
|
||||
|
||||
private fun isZipOrJar(attachment: Attachment) = attachment.openAsJAR().use { jar ->
|
||||
jar.nextEntry != null
|
||||
}
|
||||
@ -146,10 +184,10 @@ class AttachmentsClassLoader(attachments: List<Attachment>,
|
||||
// TODO - investigate potential exploits.
|
||||
private fun shouldCheckForNoOverlap(path: String, targetPlatformVersion: Int): Boolean {
|
||||
require(path.toLowerCase() == path)
|
||||
require(!path.contains("\\"))
|
||||
require(!path.contains('\\'))
|
||||
|
||||
return when {
|
||||
path.endsWith("/") -> false // Directories (packages) can overlap.
|
||||
path.endsWith('/') -> false // Directories (packages) can overlap.
|
||||
targetPlatformVersion < PlatformVersionSwitches.IGNORE_JOLOKIA_JSON_SIMPLE_IN_CORDAPPS &&
|
||||
ignoreDirectories.any { path.startsWith(it) } -> false // Ignore jolokia and json-simple for old cordapps.
|
||||
path.endsWith(".class") -> true // All class files need to be unique.
|
||||
@ -160,6 +198,7 @@ class AttachmentsClassLoader(attachments: List<Attachment>,
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ThrowsCount", "ComplexMethod", "NestedBlockDepth")
|
||||
private fun checkAttachments(attachments: List<Attachment>) {
|
||||
require(attachments.isNotEmpty()) { "attachments list is empty" }
|
||||
|
||||
@ -188,7 +227,8 @@ class AttachmentsClassLoader(attachments: List<Attachment>,
|
||||
// attacks on externally connected systems that only consider type names, we allow people to formally
|
||||
// claim their parts of the Java package namespace via registration with the zone operator.
|
||||
|
||||
val classLoaderEntries = mutableMapOf<String, SecureHash.SHA256>()
|
||||
val classLoaderEntries = mutableMapOf<String, SecureHash>()
|
||||
val ctx = AttachmentHashContext(sampleTxId)
|
||||
for (attachment in attachments) {
|
||||
// We may have been given an attachment loaded from the database in which case, important info like
|
||||
// signers is already calculated.
|
||||
@ -206,10 +246,12 @@ class AttachmentsClassLoader(attachments: List<Attachment>,
|
||||
// signed by the owners of the packages, even if it's not. We'd eventually discover that fact
|
||||
// when trying to read the class file to use it, but if we'd made any decisions based on
|
||||
// perceived correctness of the signatures or package ownership already, that would be too late.
|
||||
attachment.openAsJAR().use { JarSignatureCollector.collectSigners(it) }
|
||||
attachment.openAsJAR().use(JarSignatureCollector::collectSigners)
|
||||
}
|
||||
|
||||
// Now open it again to compute the overlap and package ownership data.
|
||||
attachment.openAsJAR().use { jar ->
|
||||
|
||||
val targetPlatformVersion = jar.manifest?.targetPlatformVersion ?: 1
|
||||
while (true) {
|
||||
val entry = jar.nextJarEntry ?: break
|
||||
@ -250,13 +292,9 @@ class AttachmentsClassLoader(attachments: List<Attachment>,
|
||||
if (!shouldCheckForNoOverlap(path, targetPlatformVersion)) continue
|
||||
|
||||
// This calculates the hash of the current entry because the JarInputStream returns only the current entry.
|
||||
fun entryHash() = ByteArrayOutputStream().use {
|
||||
jar.copyTo(it)
|
||||
it.toByteArray()
|
||||
}.sha256()
|
||||
val currentHash = hash(jar, ctx)
|
||||
|
||||
// If 2 entries are identical, it means the same file is present in both attachments, so that is ok.
|
||||
val currentHash = entryHash()
|
||||
val previousFileHash = classLoaderEntries[path]
|
||||
when {
|
||||
previousFileHash == null -> {
|
||||
@ -279,11 +317,11 @@ class AttachmentsClassLoader(attachments: List<Attachment>,
|
||||
* Required to prevent classes that were excluded from the no-overlap check from being loaded by contract code.
|
||||
* As it can lead to non-determinism.
|
||||
*/
|
||||
override fun loadClass(name: String?): Class<*> {
|
||||
if (ignorePackages.any { name!!.startsWith(it) }) {
|
||||
override fun loadClass(name: String, resolve: Boolean): Class<*>? {
|
||||
if (ignorePackages.any { name.startsWith(it) }) {
|
||||
throw ClassNotFoundException(name)
|
||||
}
|
||||
return super.loadClass(name)
|
||||
return super.loadClass(name, resolve)
|
||||
}
|
||||
}
|
||||
|
||||
@ -293,7 +331,7 @@ class AttachmentsClassLoader(attachments: List<Attachment>,
|
||||
*/
|
||||
@VisibleForTesting
|
||||
object AttachmentsClassLoaderBuilder {
|
||||
const val CACHE_SIZE = 16
|
||||
private const val CACHE_SIZE = 16
|
||||
|
||||
private val fallBackCache: AttachmentsClassLoaderCache = AttachmentsClassLoaderSimpleCacheImpl(CACHE_SIZE)
|
||||
|
||||
@ -309,13 +347,13 @@ object AttachmentsClassLoaderBuilder {
|
||||
isAttachmentTrusted: (Attachment) -> Boolean,
|
||||
parent: ClassLoader = ClassLoader.getSystemClassLoader(),
|
||||
attachmentsClassLoaderCache: AttachmentsClassLoaderCache?,
|
||||
block: (ClassLoader) -> T): T {
|
||||
val attachmentIds = attachments.map(Attachment::id).toSet()
|
||||
block: (SerializationContext) -> T): T {
|
||||
val attachmentIds = attachments.mapTo(LinkedHashSet(), Attachment::id)
|
||||
|
||||
val cache = attachmentsClassLoaderCache ?: fallBackCache
|
||||
val serializationContext = cache.computeIfAbsent(AttachmentsClassLoaderKey(attachmentIds, params), Function {
|
||||
val serializationContext = cache.computeIfAbsent(AttachmentsClassLoaderKey(attachmentIds, params), Function { key ->
|
||||
// Create classloader and load serializers, whitelisted classes
|
||||
val transactionClassLoader = AttachmentsClassLoader(attachments, params, txId, isAttachmentTrusted, parent)
|
||||
val transactionClassLoader = AttachmentsClassLoader(attachments, key.params, txId, isAttachmentTrusted, parent)
|
||||
val serializers = try {
|
||||
createInstancesOfClassesImplementing(transactionClassLoader, SerializationCustomSerializer::class.java,
|
||||
JDK1_2_CLASS_FILE_FORMAT_MAJOR_VERSION..JDK8_CLASS_FILE_FORMAT_MAJOR_VERSION)
|
||||
@ -336,11 +374,16 @@ object AttachmentsClassLoaderBuilder {
|
||||
.withWhitelist(whitelistedClasses)
|
||||
.withCustomSerializers(serializers)
|
||||
.withoutCarpenter()
|
||||
})
|
||||
}).withProperties(mapOf<Any, Any>(
|
||||
// Duplicate the SerializationContext from the cache and give
|
||||
// it these extra properties, just for this transaction.
|
||||
AMQP_ENVELOPE_CACHE_PROPERTY to HashMap<Any, Any>(AMQP_ENVELOPE_CACHE_INITIAL_CAPACITY),
|
||||
DESERIALIZATION_CACHE_PROPERTY to HashMap<Any, Any>()
|
||||
))
|
||||
|
||||
// Deserialize all relevant classes in the transaction classloader.
|
||||
return SerializationFactory.defaultFactory.withCurrentContext(serializationContext) {
|
||||
block(serializationContext.deserializationClassLoader)
|
||||
block(serializationContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -465,4 +508,4 @@ private class AttachmentURLConnection(url: URL, private val attachment: Attachme
|
||||
override fun connect() {
|
||||
connected = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ data class ContractUpgradeWireTransaction(
|
||||
|
||||
private fun upgradedContract(className: ContractClassName, classLoader: ClassLoader): UpgradedContract<ContractState, ContractState> = try {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
classLoader.loadClass(className).asSubclass(UpgradedContract::class.java).getDeclaredConstructor().newInstance() as UpgradedContract<ContractState, ContractState>
|
||||
Class.forName(className, false, classLoader).asSubclass(UpgradedContract::class.java).getDeclaredConstructor().newInstance() as UpgradedContract<ContractState, ContractState>
|
||||
} catch (e: Exception) {
|
||||
throw TransactionVerificationException.ContractCreationError(id, className, e)
|
||||
}
|
||||
@ -166,9 +166,9 @@ data class ContractUpgradeWireTransaction(
|
||||
params,
|
||||
id,
|
||||
{ (services as ServiceHubCoreInternal).attachmentTrustCalculator.calculate(it) },
|
||||
attachmentsClassLoaderCache = (services as ServiceHubCoreInternal).attachmentsClassLoaderCache) { transactionClassLoader ->
|
||||
attachmentsClassLoaderCache = (services as ServiceHubCoreInternal).attachmentsClassLoaderCache) { serializationContext ->
|
||||
val resolvedInput = binaryInput.deserialize()
|
||||
val upgradedContract = upgradedContract(upgradedContractClassName, transactionClassLoader)
|
||||
val upgradedContract = upgradedContract(upgradedContractClassName, serializationContext.deserializationClassLoader)
|
||||
val outputState = calculateUpgradedState(resolvedInput, upgradedContract, upgradedAttachment)
|
||||
outputState.serialize()
|
||||
}
|
||||
@ -311,8 +311,7 @@ private constructor(
|
||||
@CordaInternal
|
||||
internal fun loadUpgradedContract(upgradedContractClassName: ContractClassName, classLoader: ClassLoader): UpgradedContract<ContractState, *> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return classLoader
|
||||
.loadClass(upgradedContractClassName)
|
||||
return Class.forName(upgradedContractClassName, false, classLoader)
|
||||
.asSubclass(Contract::class.java)
|
||||
.getConstructor()
|
||||
.newInstance() as UpgradedContract<ContractState, *>
|
||||
|
@ -18,21 +18,25 @@ import net.corda.core.crypto.DigestService
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.BasicVerifier
|
||||
import net.corda.core.internal.AbstractVerifier
|
||||
import net.corda.core.internal.SerializedStateAndRef
|
||||
import net.corda.core.internal.Verifier
|
||||
import net.corda.core.internal.castIfPossible
|
||||
import net.corda.core.internal.deserialiseCommands
|
||||
import net.corda.core.internal.deserialiseComponentGroup
|
||||
import net.corda.core.internal.eagerDeserialise
|
||||
import net.corda.core.internal.isUploaderTrusted
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializationFactory
|
||||
import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
|
||||
import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import java.util.Collections.unmodifiableList
|
||||
import java.util.function.Predicate
|
||||
import java.util.function.Supplier
|
||||
|
||||
/**
|
||||
* A LedgerTransaction is derived from a [WireTransaction]. It is the result of doing the following operations:
|
||||
@ -90,7 +94,7 @@ private constructor(
|
||||
private val serializedInputs: List<SerializedStateAndRef>?,
|
||||
private val serializedReferences: List<SerializedStateAndRef>?,
|
||||
private val isAttachmentTrusted: (Attachment) -> Boolean,
|
||||
private val verifierFactory: (LedgerTransaction, ClassLoader) -> Verifier,
|
||||
private val verifierFactory: (LedgerTransaction, SerializationContext) -> Verifier,
|
||||
private val attachmentsClassLoaderCache: AttachmentsClassLoaderCache?,
|
||||
val digestService: DigestService
|
||||
) : FullTransaction() {
|
||||
@ -100,22 +104,23 @@ private constructor(
|
||||
*/
|
||||
@DeprecatedConstructorForDeserialization(1)
|
||||
private constructor(
|
||||
inputs: List<StateAndRef<ContractState>>,
|
||||
outputs: List<TransactionState<ContractState>>,
|
||||
commands: List<CommandWithParties<CommandData>>,
|
||||
attachments: List<Attachment>,
|
||||
id: SecureHash,
|
||||
notary: Party?,
|
||||
timeWindow: TimeWindow?,
|
||||
privacySalt: PrivacySalt,
|
||||
networkParameters: NetworkParameters?,
|
||||
references: List<StateAndRef<ContractState>>,
|
||||
componentGroups: List<ComponentGroup>?,
|
||||
serializedInputs: List<SerializedStateAndRef>?,
|
||||
serializedReferences: List<SerializedStateAndRef>?,
|
||||
isAttachmentTrusted: (Attachment) -> Boolean,
|
||||
verifierFactory: (LedgerTransaction, ClassLoader) -> Verifier,
|
||||
attachmentsClassLoaderCache: AttachmentsClassLoaderCache?) : this(
|
||||
inputs: List<StateAndRef<ContractState>>,
|
||||
outputs: List<TransactionState<ContractState>>,
|
||||
commands: List<CommandWithParties<CommandData>>,
|
||||
attachments: List<Attachment>,
|
||||
id: SecureHash,
|
||||
notary: Party?,
|
||||
timeWindow: TimeWindow?,
|
||||
privacySalt: PrivacySalt,
|
||||
networkParameters: NetworkParameters?,
|
||||
references: List<StateAndRef<ContractState>>,
|
||||
componentGroups: List<ComponentGroup>?,
|
||||
serializedInputs: List<SerializedStateAndRef>?,
|
||||
serializedReferences: List<SerializedStateAndRef>?,
|
||||
isAttachmentTrusted: (Attachment) -> Boolean,
|
||||
verifierFactory: (LedgerTransaction, SerializationContext) -> Verifier,
|
||||
attachmentsClassLoaderCache: AttachmentsClassLoaderCache?
|
||||
) : this(
|
||||
inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt,
|
||||
networkParameters, references, componentGroups, serializedInputs, serializedReferences,
|
||||
isAttachmentTrusted, verifierFactory, attachmentsClassLoaderCache, DigestService.sha2_256)
|
||||
@ -124,8 +129,8 @@ private constructor(
|
||||
companion object {
|
||||
private val logger = contextLogger()
|
||||
|
||||
private fun <T> protect(list: List<T>?): List<T>? {
|
||||
return list?.run {
|
||||
private fun <T> protect(list: List<T>): List<T> {
|
||||
return list.run {
|
||||
if (isEmpty()) {
|
||||
emptyList()
|
||||
} else {
|
||||
@ -134,6 +139,8 @@ private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> protectOrNull(list: List<T>?): List<T>? = list?.let(::protect)
|
||||
|
||||
@CordaInternal
|
||||
internal fun create(
|
||||
inputs: List<StateAndRef<ContractState>>,
|
||||
@ -164,9 +171,9 @@ private constructor(
|
||||
privacySalt = privacySalt,
|
||||
networkParameters = networkParameters,
|
||||
references = references,
|
||||
componentGroups = protect(componentGroups),
|
||||
serializedInputs = protect(serializedInputs),
|
||||
serializedReferences = protect(serializedReferences),
|
||||
componentGroups = protectOrNull(componentGroups),
|
||||
serializedInputs = protectOrNull(serializedInputs),
|
||||
serializedReferences = protectOrNull(serializedReferences),
|
||||
isAttachmentTrusted = isAttachmentTrusted,
|
||||
verifierFactory = ::BasicVerifier,
|
||||
attachmentsClassLoaderCache = attachmentsClassLoaderCache,
|
||||
@ -176,10 +183,11 @@ private constructor(
|
||||
|
||||
/**
|
||||
* This factory function will create an instance of [LedgerTransaction]
|
||||
* that will be used inside the DJVM sandbox.
|
||||
* that will be used for contract verification. See [BasicVerifier] and
|
||||
* [DeterministicVerifier][net.corda.node.internal.djvm.DeterministicVerifier].
|
||||
*/
|
||||
@CordaInternal
|
||||
fun createForSandbox(
|
||||
fun createForContractVerify(
|
||||
inputs: List<StateAndRef<ContractState>>,
|
||||
outputs: List<TransactionState<ContractState>>,
|
||||
commands: List<CommandWithParties<CommandData>>,
|
||||
@ -188,28 +196,31 @@ private constructor(
|
||||
notary: Party?,
|
||||
timeWindow: TimeWindow?,
|
||||
privacySalt: PrivacySalt,
|
||||
networkParameters: NetworkParameters,
|
||||
networkParameters: NetworkParameters?,
|
||||
references: List<StateAndRef<ContractState>>,
|
||||
digestService: DigestService): LedgerTransaction {
|
||||
return LedgerTransaction(
|
||||
inputs = inputs,
|
||||
outputs = outputs,
|
||||
commands = commands,
|
||||
attachments = attachments,
|
||||
inputs = protect(inputs),
|
||||
outputs = protect(outputs),
|
||||
commands = protect(commands),
|
||||
attachments = protect(attachments),
|
||||
id = id,
|
||||
notary = notary,
|
||||
timeWindow = timeWindow,
|
||||
privacySalt = privacySalt,
|
||||
networkParameters = networkParameters,
|
||||
references = references,
|
||||
references = protect(references),
|
||||
componentGroups = null,
|
||||
serializedInputs = null,
|
||||
serializedReferences = null,
|
||||
isAttachmentTrusted = { true },
|
||||
verifierFactory = ::BasicVerifier,
|
||||
verifierFactory = ::NoOpVerifier,
|
||||
attachmentsClassLoaderCache = null,
|
||||
digestService = digestService
|
||||
)
|
||||
// This check accesses input states and must run on the LedgerTransaction
|
||||
// instance that is verified, not on the outer LedgerTransaction shell.
|
||||
// All states must also deserialize using the correct SerializationContext.
|
||||
).also(LedgerTransaction::checkBaseInvariants)
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,11 +262,17 @@ private constructor(
|
||||
getParamsWithGoo(),
|
||||
id,
|
||||
isAttachmentTrusted = isAttachmentTrusted,
|
||||
attachmentsClassLoaderCache = attachmentsClassLoaderCache) { transactionClassLoader ->
|
||||
// Create a copy of the outer LedgerTransaction which deserializes all fields inside the [transactionClassLoader].
|
||||
attachmentsClassLoaderCache = attachmentsClassLoaderCache) { serializationContext ->
|
||||
|
||||
// Legacy check - warns if the LedgerTransaction was created incorrectly.
|
||||
checkLtxForVerification()
|
||||
|
||||
// Create a copy of the outer LedgerTransaction which deserializes all fields using
|
||||
// the serialization context (or its deserializationClassloader).
|
||||
// Only the copy will be used for verification, and the outer shell will be discarded.
|
||||
// This artifice is required to preserve backwards compatibility.
|
||||
verifierFactory(createLtxForVerification(), transactionClassLoader)
|
||||
// NOTE: The Verifier creates the copies of the LedgerTransaction object now.
|
||||
verifierFactory(this, serializationContext)
|
||||
}
|
||||
}
|
||||
|
||||
@ -272,7 +289,7 @@ private constructor(
|
||||
* Node without changing either the wire format or any public APIs.
|
||||
*/
|
||||
@CordaInternal
|
||||
fun specialise(alternateVerifier: (LedgerTransaction, ClassLoader) -> Verifier): LedgerTransaction = LedgerTransaction(
|
||||
fun specialise(alternateVerifier: (LedgerTransaction, SerializationContext) -> Verifier): LedgerTransaction = LedgerTransaction(
|
||||
inputs = inputs,
|
||||
outputs = outputs,
|
||||
commands = commands,
|
||||
@ -287,7 +304,11 @@ private constructor(
|
||||
serializedInputs = serializedInputs,
|
||||
serializedReferences = serializedReferences,
|
||||
isAttachmentTrusted = isAttachmentTrusted,
|
||||
verifierFactory = alternateVerifier,
|
||||
verifierFactory = if (verifierFactory == ::NoOpVerifier) {
|
||||
throw IllegalStateException("Cannot specialise transaction while verifying contracts")
|
||||
} else {
|
||||
alternateVerifier
|
||||
},
|
||||
attachmentsClassLoaderCache = attachmentsClassLoaderCache,
|
||||
digestService = digestService
|
||||
)
|
||||
@ -319,58 +340,12 @@ private constructor(
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the [LedgerTransaction] instance that will be used by contract verification.
|
||||
*
|
||||
* This method needs to run in the special transaction attachments classloader context.
|
||||
*/
|
||||
private fun createLtxForVerification(): LedgerTransaction {
|
||||
val serializedInputs = this.serializedInputs
|
||||
val serializedReferences = this.serializedReferences
|
||||
val componentGroups = this.componentGroups
|
||||
|
||||
val transaction= if (serializedInputs != null && serializedReferences != null && componentGroups != null) {
|
||||
// Deserialize all relevant classes in the transaction classloader.
|
||||
val deserializedInputs = serializedInputs.map { it.toStateAndRef() }
|
||||
val deserializedReferences = serializedReferences.map { it.toStateAndRef() }
|
||||
val deserializedOutputs = deserialiseComponentGroup(componentGroups, TransactionState::class, ComponentGroupEnum.OUTPUTS_GROUP, forceDeserialize = true)
|
||||
val deserializedCommands = deserialiseCommands(componentGroups, forceDeserialize = true, digestService = digestService)
|
||||
val authenticatedDeserializedCommands = deserializedCommands.map { cmd ->
|
||||
@Suppress("DEPRECATION") // Deprecated feature.
|
||||
val parties = commands.find { it.value.javaClass.name == cmd.value.javaClass.name }!!.signingParties
|
||||
CommandWithParties(cmd.signers, parties, cmd.value)
|
||||
}
|
||||
|
||||
LedgerTransaction(
|
||||
inputs = deserializedInputs,
|
||||
outputs = deserializedOutputs,
|
||||
commands = authenticatedDeserializedCommands,
|
||||
attachments = this.attachments,
|
||||
id = this.id,
|
||||
notary = this.notary,
|
||||
timeWindow = this.timeWindow,
|
||||
privacySalt = this.privacySalt,
|
||||
networkParameters = this.networkParameters,
|
||||
references = deserializedReferences,
|
||||
componentGroups = componentGroups,
|
||||
serializedInputs = serializedInputs,
|
||||
serializedReferences = serializedReferences,
|
||||
isAttachmentTrusted = isAttachmentTrusted,
|
||||
verifierFactory = verifierFactory,
|
||||
attachmentsClassLoaderCache = attachmentsClassLoaderCache,
|
||||
digestService = digestService
|
||||
)
|
||||
} else {
|
||||
// This branch is only present for backwards compatibility.
|
||||
private fun checkLtxForVerification() {
|
||||
if (serializedInputs == null || serializedReferences == null || componentGroups == null) {
|
||||
logger.warn("The LedgerTransaction should not be instantiated directly from client code. Please use WireTransaction.toLedgerTransaction." +
|
||||
"The result of the verify method might not be accurate.")
|
||||
this
|
||||
}
|
||||
|
||||
// This check accesses input states and must be run in this context.
|
||||
// It must run on the instance that is verified, not on the outer LedgerTransaction shell.
|
||||
transaction.checkBaseInvariants()
|
||||
|
||||
return transaction
|
||||
}
|
||||
|
||||
/**
|
||||
@ -740,7 +715,7 @@ private constructor(
|
||||
componentGroups = null,
|
||||
serializedInputs = null,
|
||||
serializedReferences = null,
|
||||
isAttachmentTrusted = { it.isUploaderTrusted() },
|
||||
isAttachmentTrusted = Attachment::isUploaderTrusted,
|
||||
verifierFactory = ::BasicVerifier,
|
||||
attachmentsClassLoaderCache = null
|
||||
)
|
||||
@ -770,7 +745,7 @@ private constructor(
|
||||
componentGroups = null,
|
||||
serializedInputs = null,
|
||||
serializedReferences = null,
|
||||
isAttachmentTrusted = { it.isUploaderTrusted() },
|
||||
isAttachmentTrusted = Attachment::isUploaderTrusted,
|
||||
verifierFactory = ::BasicVerifier,
|
||||
attachmentsClassLoaderCache = null
|
||||
)
|
||||
@ -838,3 +813,80 @@ private constructor(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the default [Verifier] that configures Corda
|
||||
* to execute [Contract.verify(LedgerTransaction)].
|
||||
*
|
||||
* THIS CLASS IS NOT PUBLIC API, AND IS DELIBERATELY PRIVATE!
|
||||
*/
|
||||
@CordaInternal
|
||||
private class BasicVerifier(
|
||||
ltx: LedgerTransaction,
|
||||
private val serializationContext: SerializationContext
|
||||
) : AbstractVerifier(ltx, serializationContext.deserializationClassLoader) {
|
||||
|
||||
init {
|
||||
// This is a sanity check: We should only instantiate this
|
||||
// class from [LedgerTransaction.internalPrepareVerify].
|
||||
require(serializationContext === SerializationFactory.defaultFactory.currentContext) {
|
||||
"BasicVerifier for TX ${ltx.id} created outside its SerializationContext"
|
||||
}
|
||||
|
||||
// Fetch these commands' signing parties from the database.
|
||||
// Corda forbids database access during contract verification,
|
||||
// and so we must load the commands here eagerly instead.
|
||||
// THIS ALSO DESERIALISES THE COMMANDS USING THE WRONG CONTEXT
|
||||
// BECAUSE THAT CONTEXT WAS CHOSEN WHEN THE LAZY MAP WAS CREATED,
|
||||
// AND CHANGING THE DEFAULT CONTEXT HERE DOES NOT AFFECT IT.
|
||||
ltx.commands.eagerDeserialise()
|
||||
}
|
||||
|
||||
override val transaction: Supplier<LedgerTransaction>
|
||||
get() = Supplier(::createTransaction)
|
||||
|
||||
private fun createTransaction(): LedgerTransaction {
|
||||
// Deserialize all relevant classes using the serializationContext.
|
||||
return SerializationFactory.defaultFactory.withCurrentContext(serializationContext) {
|
||||
ltx.transform { componentGroups, serializedInputs, serializedReferences ->
|
||||
val deserializedInputs = serializedInputs.map(SerializedStateAndRef::toStateAndRef)
|
||||
val deserializedReferences = serializedReferences.map(SerializedStateAndRef::toStateAndRef)
|
||||
val deserializedOutputs = deserialiseComponentGroup(componentGroups, TransactionState::class, ComponentGroupEnum.OUTPUTS_GROUP, forceDeserialize = true)
|
||||
val deserializedCommands = deserialiseCommands(componentGroups, forceDeserialize = true, digestService = ltx.digestService)
|
||||
val authenticatedDeserializedCommands = deserializedCommands.mapIndexed { idx, cmd ->
|
||||
// Requires ltx.commands to have been deserialized already.
|
||||
@Suppress("DEPRECATION") // Deprecated feature.
|
||||
val parties = ltx.commands[idx].signingParties
|
||||
CommandWithParties(cmd.signers, parties, cmd.value)
|
||||
}
|
||||
|
||||
LedgerTransaction.createForContractVerify(
|
||||
inputs = deserializedInputs,
|
||||
outputs = deserializedOutputs,
|
||||
commands = authenticatedDeserializedCommands,
|
||||
attachments = ltx.attachments,
|
||||
id = ltx.id,
|
||||
notary = ltx.notary,
|
||||
timeWindow = ltx.timeWindow,
|
||||
privacySalt = ltx.privacySalt,
|
||||
networkParameters = ltx.networkParameters,
|
||||
references = deserializedReferences,
|
||||
digestService = ltx.digestService
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A "do nothing" [Verifier] installed for contract verification.
|
||||
*
|
||||
* THIS CLASS IS NOT PUBLIC API, AND IS DELIBERATELY PRIVATE!
|
||||
*/
|
||||
@Suppress("unused_parameter")
|
||||
@CordaInternal
|
||||
private class NoOpVerifier(ltx: LedgerTransaction, serializationContext: SerializationContext) : Verifier {
|
||||
// Invoking LedgerTransaction.verify() from Contract.verify(LedgerTransaction)
|
||||
// will execute this function. But why would anyone do that?!
|
||||
override fun verify() {}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
|
||||
import net.corda.core.serialization.SerializationFactory
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
|
||||
import net.corda.core.serialization.serialize
|
||||
@ -154,7 +155,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
||||
resolveAttachment,
|
||||
{ stateRef -> resolveStateRef(stateRef)?.serialize() },
|
||||
{ null },
|
||||
{ it.isUploaderTrusted() },
|
||||
Attachment::isUploaderTrusted,
|
||||
null
|
||||
)
|
||||
}
|
||||
@ -187,19 +188,26 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
||||
): LedgerTransaction {
|
||||
// Look up public keys to authenticated identities.
|
||||
val authenticatedCommands = commands.lazyMapped { cmd, _ ->
|
||||
val parties = cmd.signers.mapNotNull { pk -> resolveIdentity(pk) }
|
||||
val parties = cmd.signers.mapNotNull(resolveIdentity)
|
||||
CommandWithParties(cmd.signers, parties, cmd.value)
|
||||
}
|
||||
|
||||
// Ensure that the lazy mappings will use the correct SerializationContext.
|
||||
val serializationFactory = SerializationFactory.defaultFactory
|
||||
val serializationContext = serializationFactory.defaultContext
|
||||
val toStateAndRef = { ssar: SerializedStateAndRef, _: Int ->
|
||||
ssar.toStateAndRef(serializationFactory, serializationContext)
|
||||
}
|
||||
|
||||
val serializedResolvedInputs = inputs.map { ref ->
|
||||
SerializedStateAndRef(resolveStateRefAsSerialized(ref) ?: throw TransactionResolutionException(ref.txhash), ref)
|
||||
}
|
||||
val resolvedInputs = serializedResolvedInputs.lazyMapped { star, _ -> star.toStateAndRef() }
|
||||
val resolvedInputs = serializedResolvedInputs.lazyMapped(toStateAndRef)
|
||||
|
||||
val serializedResolvedReferences = references.map { ref ->
|
||||
SerializedStateAndRef(resolveStateRefAsSerialized(ref) ?: throw TransactionResolutionException(ref.txhash), ref)
|
||||
}
|
||||
val resolvedReferences = serializedResolvedReferences.lazyMapped { star, _ -> star.toStateAndRef() }
|
||||
val resolvedReferences = serializedResolvedReferences.lazyMapped(toStateAndRef)
|
||||
|
||||
val resolvedAttachments = attachments.lazyMapped { att, _ -> resolveAttachment(att) ?: throw AttachmentResolutionException(att) }
|
||||
|
||||
@ -214,7 +222,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
||||
notary,
|
||||
timeWindow,
|
||||
privacySalt,
|
||||
resolvedNetworkParameters,
|
||||
resolvedNetworkParameters.toImmutable(),
|
||||
resolvedReferences,
|
||||
componentGroups,
|
||||
serializedResolvedInputs,
|
||||
|
@ -0,0 +1,54 @@
|
||||
package net.corda.core.internal.utilities
|
||||
|
||||
import net.corda.core.obfuscator.XorOutputStream
|
||||
import java.net.URL
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
object TestResourceWriter {
|
||||
|
||||
private val externalZipBombUrls = arrayOf(
|
||||
URL("https://www.bamsoftware.com/hacks/zipbomb/zbsm.zip"),
|
||||
URL("https://www.bamsoftware.com/hacks/zipbomb/zblg.zip"),
|
||||
URL("https://www.bamsoftware.com/hacks/zipbomb/zbxl.zip")
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@Suppress("NestedBlockDepth", "MagicNumber")
|
||||
fun main(vararg args : String) {
|
||||
for(arg in args) {
|
||||
/**
|
||||
* Download zip bombs
|
||||
*/
|
||||
for(url in externalZipBombUrls) {
|
||||
url.openStream().use { inputStream ->
|
||||
val destination = Paths.get(arg).resolve(Paths.get(url.path + ".xor").fileName)
|
||||
Files.newOutputStream(destination).buffered().let(::XorOutputStream).use { outputStream ->
|
||||
inputStream.copyTo(outputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Create a jar archive with a huge manifest file, used in unit tests to check that it is also identified as a zip bomb.
|
||||
* This is because {@link java.util.jar.JarInputStream}
|
||||
* <a href="https://github.com/openjdk/jdk/blob/4dedba9ebe11750f4b39c41feb4a4314ccdb3a08/src/java.base/share/classes/java/util/jar/JarInputStream.java#L95">eagerly loads the manifest file in memory</a>
|
||||
* which would make such a jar dangerous if used as an attachment
|
||||
*/
|
||||
val destination = Paths.get(arg).resolve(Paths.get("big-manifest.jar.xor").fileName)
|
||||
ZipOutputStream(XorOutputStream((Files.newOutputStream(destination).buffered()))).use { zos ->
|
||||
val zipEntry = ZipEntry("MANIFEST.MF")
|
||||
zipEntry.method = ZipEntry.DEFLATED
|
||||
zos.putNextEntry(zipEntry)
|
||||
val buffer = ByteArray(0x100000) { 0x0 }
|
||||
var written = 0L
|
||||
while(written < 10_000_000_000) {
|
||||
zos.write(buffer)
|
||||
written += buffer.size
|
||||
}
|
||||
zos.closeEntry()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package net.corda.core.obfuscator
|
||||
|
||||
import java.io.FilterInputStream
|
||||
import java.io.InputStream
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
class XorInputStream(private val source : InputStream) : FilterInputStream(source) {
|
||||
var prev : Int = 0
|
||||
|
||||
override fun read(): Int {
|
||||
prev = source.read() xor prev
|
||||
return prev - 0x80
|
||||
}
|
||||
|
||||
override fun read(buffer: ByteArray): Int {
|
||||
return read(buffer, 0, buffer.size)
|
||||
}
|
||||
|
||||
override fun read(buffer: ByteArray, off: Int, len: Int): Int {
|
||||
var read = 0
|
||||
while(true) {
|
||||
val b = source.read()
|
||||
if(b < 0) break
|
||||
buffer[off + read++] = ((b xor prev) - 0x80).toByte()
|
||||
prev = b
|
||||
if(read == len) break
|
||||
}
|
||||
return read
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package net.corda.core.obfuscator
|
||||
|
||||
import java.io.FilterOutputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
class XorOutputStream(private val destination : OutputStream) : FilterOutputStream(destination) {
|
||||
var prev : Int = 0
|
||||
|
||||
override fun write(byte: Int) {
|
||||
val b = (byte + 0x80) xor prev
|
||||
destination.write(b)
|
||||
prev = b
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteArray) {
|
||||
write(buffer, 0, buffer.size)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteArray, off: Int, len: Int) {
|
||||
var written = 0
|
||||
while(true) {
|
||||
val b = (buffer[written] + 0x80) xor prev
|
||||
destination.write(b)
|
||||
prev = b
|
||||
++written
|
||||
if(written == len) break
|
||||
}
|
||||
}
|
||||
}
|
@ -7,6 +7,18 @@ The Corda core module defines a lot of types and helpers that can only be exerci
|
||||
the context of a node. However, as everything else depends on the core module, we cannot pull the node into
|
||||
this module. Therefore, any tests that require further Corda dependencies need to be defined in the module
|
||||
`core-tests`, which has the full set of dependencies including `node-driver`.
|
||||
|
||||
|
||||
# ZipBomb tests
|
||||
|
||||
There is a unit test that checks the zip bomb detector in `net.corda.core.internal.utilities.ZipBombDetector` works correctly.
|
||||
This test (`core/src/test/kotlin/net/corda/core/internal/utilities/ZipBombDetectorTest.kt`) uses real zip bombs, provided by `https://www.bamsoftware.com/hacks/zipbomb/`.
|
||||
As it is undesirable to have unit test depends on external internet resources we do not control, those files are included as resources in
|
||||
`core/src/test/resources/zip/`, however some Windows antivirus software correctly identifies those files as zip bombs,
|
||||
raising an alert to the user. To mitigate this, those files have been obfuscated using `net.corda.core.obfuscator.XorOutputStream`
|
||||
(which simply XORs every byte of the file with the previous one, except for the first byte that is XORed with zero)
|
||||
to prevent antivirus software from detecting them as zip bombs and are de-obfuscated on the fly in unit tests using
|
||||
`net.corda.core.obfuscator.XorInputStream`.
|
||||
|
||||
There is a dedicated Gradle task to re-download and re-obfuscate all the test resource files named `writeTestResources`,
|
||||
its source code is in `core/src/obfuscator/kotlin/net/corda/core/internal/utilities/TestResourceWriter.kt`
|
||||
|
@ -5,10 +5,12 @@ import net.corda.core.crypto.DigestService
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
|
||||
import net.corda.core.transactions.ComponentGroup
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import java.util.function.Supplier
|
||||
|
||||
/**
|
||||
* A set of functions in core:test that allows testing of core internal classes in the core-tests project.
|
||||
@ -38,7 +40,17 @@ fun createLedgerTransaction(
|
||||
isAttachmentTrusted: (Attachment) -> Boolean,
|
||||
attachmentsClassLoaderCache: AttachmentsClassLoaderCache,
|
||||
digestService: DigestService = DigestService.default
|
||||
): LedgerTransaction = LedgerTransaction.create(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references, componentGroups, serializedInputs, serializedReferences, isAttachmentTrusted, attachmentsClassLoaderCache, digestService)
|
||||
): LedgerTransaction = LedgerTransaction.create(
|
||||
inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references, componentGroups, serializedInputs, serializedReferences, isAttachmentTrusted, attachmentsClassLoaderCache, digestService
|
||||
).specialise(::PassthroughVerifier)
|
||||
|
||||
fun createContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable) = TransactionVerificationException.ContractCreationError(txId, contractClass, cause)
|
||||
fun createContractRejection(txId: SecureHash, contract: Contract, cause: Throwable) = TransactionVerificationException.ContractRejection(txId, contract, cause)
|
||||
|
||||
/**
|
||||
* Verify the [LedgerTransaction] we already have.
|
||||
*/
|
||||
private class PassthroughVerifier(ltx: LedgerTransaction, context: SerializationContext) : AbstractVerifier(ltx, context.deserializationClassLoader) {
|
||||
override val transaction: Supplier<LedgerTransaction>
|
||||
get() = Supplier { ltx }
|
||||
}
|
||||
|
@ -0,0 +1,70 @@
|
||||
package net.corda.core.internal.utilities
|
||||
|
||||
import net.corda.core.obfuscator.XorInputStream
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
class ZipBombDetectorTest(private val case : TestCase) {
|
||||
|
||||
enum class TestCase(
|
||||
val description : String,
|
||||
val zipResource : String,
|
||||
val maxUncompressedSize : Long,
|
||||
val maxCompressionRatio : Float,
|
||||
val expectedOutcome : Boolean
|
||||
) {
|
||||
LEGIT_JAR("This project's jar file", "zip/core.jar", 128_000, 10f, false),
|
||||
|
||||
// This is not detected as a zip bomb as ZipInputStream is unable to read all of its entries
|
||||
// (https://stackoverflow.com/questions/69286786/zipinputstream-cannot-parse-a-281-tb-zip-bomb),
|
||||
// so the total uncompressed size doesn't exceed maxUncompressedSize
|
||||
SMALL_BOMB(
|
||||
"A large (5.5 GB) zip archive",
|
||||
"zip/zbsm.zip.xor", 64_000_000, 10f, false),
|
||||
|
||||
// Decreasing maxUncompressedSize leads to a successful detection
|
||||
SMALL_BOMB2(
|
||||
"A large (5.5 GB) zip archive, with 1MB maxUncompressedSize",
|
||||
"zip/zbsm.zip.xor", 1_000_000, 10f, true),
|
||||
|
||||
// ZipInputStream is also unable to read all entries of zblg.zip, but since the first one is already bigger than 4GB,
|
||||
// that is enough to exceed maxUncompressedSize
|
||||
LARGE_BOMB(
|
||||
"A huge (281 TB) Zip bomb, this is the biggest possible non-recursive non-Zip64 archive",
|
||||
"zip/zblg.zip.xor", 64_000_000, 10f, true),
|
||||
|
||||
//Same for this, but its entries are 22GB each
|
||||
EXTRA_LARGE_BOMB(
|
||||
"A humongous (4.5 PB) Zip64 bomb",
|
||||
"zip/zbxl.zip.xor", 64_000_000, 10f, true),
|
||||
|
||||
//This is a jar file containing a single 10GB manifest
|
||||
BIG_MANIFEST(
|
||||
"A jar file with a huge manifest",
|
||||
"zip/big-manifest.jar.xor", 64_000_000, 10f, true);
|
||||
|
||||
override fun toString() = description
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters(name = "{0}")
|
||||
fun generateTestCases(): Collection<*> {
|
||||
return TestCase.values().toList()
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout=10_000)
|
||||
fun test() {
|
||||
(javaClass.classLoader.getResourceAsStream(case.zipResource) ?:
|
||||
throw IllegalStateException("Missing test resource file ${case.zipResource}"))
|
||||
.buffered()
|
||||
.let(::XorInputStream)
|
||||
.let {
|
||||
Assert.assertEquals(case.expectedOutcome, ZipBombDetector.scanZip(it, case.maxUncompressedSize, case.maxCompressionRatio))
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package net.corda.core.obfuscator
|
||||
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.security.DigestInputStream
|
||||
import java.security.DigestOutputStream
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
class XorStreamTest(private val size : Int) {
|
||||
private val random = Random(0)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters(name = "{0}")
|
||||
fun generateTestCases(): Collection<*> {
|
||||
return listOf(0, 16, 31, 127, 1000, 1024)
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 5000)
|
||||
fun test() {
|
||||
val baos = ByteArrayOutputStream(size)
|
||||
val md = MessageDigest.getInstance("MD5")
|
||||
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||
DigestOutputStream(XorOutputStream(baos), md).use { outputStream ->
|
||||
var written = 0
|
||||
while(written < size) {
|
||||
random.nextBytes(buffer)
|
||||
val bytesToWrite = (size - written).coerceAtMost(buffer.size)
|
||||
outputStream.write(buffer, 0, bytesToWrite)
|
||||
written += bytesToWrite
|
||||
}
|
||||
}
|
||||
val digest = md.digest()
|
||||
md.reset()
|
||||
DigestInputStream(XorInputStream(ByteArrayInputStream(baos.toByteArray())), md).use { inputStream ->
|
||||
while(true) {
|
||||
val read = inputStream.read(buffer)
|
||||
if(read <= 0) break
|
||||
}
|
||||
}
|
||||
Assert.assertArrayEquals(digest, md.digest())
|
||||
}
|
||||
}
|
BIN
core/src/test/resources/zip/big-manifest.jar.xor
Normal file
BIN
core/src/test/resources/zip/big-manifest.jar.xor
Normal file
Binary file not shown.
BIN
core/src/test/resources/zip/zblg.zip.xor
Normal file
BIN
core/src/test/resources/zip/zblg.zip.xor
Normal file
Binary file not shown.
BIN
core/src/test/resources/zip/zbsm.zip.xor
Normal file
BIN
core/src/test/resources/zip/zbsm.zip.xor
Normal file
Binary file not shown.
BIN
core/src/test/resources/zip/zbxl.zip.xor
Normal file
BIN
core/src/test/resources/zip/zbxl.zip.xor
Normal file
Binary file not shown.
@ -64,4 +64,4 @@ COPY --chown=corda:corda starting-node.conf /opt/corda/starting-node.conf
|
||||
USER "corda"
|
||||
EXPOSE ${MY_P2P_PORT} ${MY_RPC_PORT} ${MY_RPC_ADMIN_PORT}
|
||||
WORKDIR /opt/corda
|
||||
CMD ["run-corda"]
|
||||
CMD ["run-corda"]
|
||||
|
@ -5,6 +5,10 @@ apply plugin: 'net.corda.plugins.publish-utils'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'com.jfrog.artifactory'
|
||||
|
||||
dependencies {
|
||||
compile rootProject
|
||||
}
|
||||
|
||||
def internalPackagePrefixes(sourceDirs) {
|
||||
def prefixes = []
|
||||
// Kotlin allows packages to deviate from the directory structure, but let's assume they don't:
|
||||
@ -36,10 +40,13 @@ task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
|
||||
}
|
||||
|
||||
[dokka, dokkaJavadoc].collect {
|
||||
it.configure {
|
||||
it.configuration {
|
||||
moduleName = 'corda'
|
||||
processConfigurations = ['compile']
|
||||
sourceDirs = dokkaSourceDirs
|
||||
dokkaSourceDirs.collect { sourceDir ->
|
||||
sourceRoot {
|
||||
path = sourceDir.path
|
||||
}
|
||||
}
|
||||
includes = ['packages.md']
|
||||
jdkVersion = 8
|
||||
externalDocumentationLink {
|
||||
@ -52,7 +59,7 @@ task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
|
||||
url = new URL("https://www.bouncycastle.org/docs/docs1.5on/")
|
||||
}
|
||||
internalPackagePrefixes.collect { packagePrefix ->
|
||||
packageOptions {
|
||||
perPackageOption {
|
||||
prefix = packagePrefix
|
||||
suppress = true
|
||||
}
|
||||
|
@ -4,14 +4,15 @@ import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.PLATFORM_VERSION
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.nodeapi.internal.persistence.RestrictedConnection
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import net.corda.testing.node.MockNetworkParameters
|
||||
import net.corda.testing.node.StartedMockNode
|
||||
import net.corda.testing.node.internal.enclosedCordapp
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@ -38,40 +39,63 @@ class RestrictedConnectionFlowTest {
|
||||
}
|
||||
|
||||
@InitiatingFlow
|
||||
class TestCloseMethodIsBlocked : FlowLogic<Unit>() {
|
||||
class TestClearWarningsMethodIsBlocked : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
val connection = serviceHub.jdbcSession()
|
||||
connection.close()
|
||||
connection.clearWarnings()
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
fun init() {
|
||||
mockNetwork = MockNetwork(MockNetworkParameters())
|
||||
aliceNode = mockNetwork.createPartyNode(CordaX500Name("Alice", "London", "GB"))
|
||||
}
|
||||
|
||||
@After
|
||||
fun done() {
|
||||
mockNetwork.stopNodes()
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun testIfItIsRestrictedConnection() {
|
||||
fun `restricted connection is returned from ServiceHub#jdbcSession`() {
|
||||
mockNetwork = MockNetwork(MockNetworkParameters(listOf(enclosedCordapp().copy(targetPlatformVersion = PLATFORM_VERSION))))
|
||||
aliceNode = mockNetwork.createPartyNode(CordaX500Name("Alice", "London", "GB"))
|
||||
assertTrue { aliceNode.startFlow(TestIfItIsRestrictedConnection()).get() }
|
||||
mockNetwork.runNetwork()
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun testMethodsAreBlocked() {
|
||||
fun `restricted methods are blocked when the target platform is the current corda version`() {
|
||||
mockNetwork = MockNetwork(MockNetworkParameters(listOf(enclosedCordapp().copy(targetPlatformVersion = PLATFORM_VERSION))))
|
||||
aliceNode = mockNetwork.createPartyNode(CordaX500Name("Alice", "London", "GB"))
|
||||
Assertions.assertThatExceptionOfType(UnsupportedOperationException::class.java)
|
||||
.isThrownBy { aliceNode.startFlow(TestAutoCommitMethodIsBlocked()).getOrThrow() }
|
||||
.withMessageContaining("This method cannot be called via ServiceHub.jdbcSession.")
|
||||
.withMessageContaining("ServiceHub.jdbcSession.setAutoCommit is restricted and cannot be called")
|
||||
|
||||
Assertions.assertThatExceptionOfType(UnsupportedOperationException::class.java)
|
||||
.isThrownBy { aliceNode.startFlow(TestCloseMethodIsBlocked()).getOrThrow() }
|
||||
.withMessageContaining("This method cannot be called via ServiceHub.jdbcSession.")
|
||||
.isThrownBy { aliceNode.startFlow(TestClearWarningsMethodIsBlocked()).getOrThrow() }
|
||||
.withMessageContaining("ServiceHub.jdbcSession.clearWarnings is restricted and cannot be called")
|
||||
|
||||
mockNetwork.runNetwork()
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `restricted methods are blocked when the target platform is 7`() {
|
||||
mockNetwork = MockNetwork(MockNetworkParameters(listOf(enclosedCordapp().copy(targetPlatformVersion = 7))))
|
||||
aliceNode = mockNetwork.createPartyNode(CordaX500Name("Alice", "London", "GB"))
|
||||
Assertions.assertThatExceptionOfType(UnsupportedOperationException::class.java)
|
||||
.isThrownBy { aliceNode.startFlow(TestAutoCommitMethodIsBlocked()).getOrThrow() }
|
||||
.withMessageContaining("ServiceHub.jdbcSession.setAutoCommit is restricted and cannot be called")
|
||||
|
||||
Assertions.assertThatExceptionOfType(UnsupportedOperationException::class.java)
|
||||
.isThrownBy { aliceNode.startFlow(TestClearWarningsMethodIsBlocked()).getOrThrow() }
|
||||
.withMessageContaining("ServiceHub.jdbcSession.clearWarnings is restricted and cannot be called")
|
||||
|
||||
mockNetwork.runNetwork()
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `restricted methods are not blocked when the target platform is 6`() {
|
||||
mockNetwork = MockNetwork(MockNetworkParameters(listOf(enclosedCordapp().copy(targetPlatformVersion = 6))))
|
||||
aliceNode = mockNetwork.createPartyNode(CordaX500Name("Alice", "London", "GB"))
|
||||
aliceNode.startFlow(TestAutoCommitMethodIsBlocked()).getOrThrow()
|
||||
aliceNode.startFlow(TestClearWarningsMethodIsBlocked()).getOrThrow()
|
||||
|
||||
mockNetwork.runNetwork()
|
||||
}
|
||||
|
@ -4,14 +4,15 @@ import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.PLATFORM_VERSION
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.nodeapi.internal.persistence.RestrictedEntityManager
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import net.corda.testing.node.MockNetworkParameters
|
||||
import net.corda.testing.node.StartedMockNode
|
||||
import net.corda.testing.node.internal.enclosedCordapp
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@ -25,7 +26,7 @@ class RestrictedEntityManagerFlowTest {
|
||||
@Suspendable
|
||||
override fun call() : Boolean {
|
||||
var result = false
|
||||
serviceHub.withEntityManager() {
|
||||
serviceHub.withEntityManager {
|
||||
result = this is RestrictedEntityManager
|
||||
}
|
||||
return result
|
||||
@ -33,11 +34,11 @@ class RestrictedEntityManagerFlowTest {
|
||||
}
|
||||
|
||||
@InitiatingFlow
|
||||
class TestCloseMethodIsBlocked : FlowLogic<Unit>() {
|
||||
class TestGetMetamodelMethodIsBlocked : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
serviceHub.withEntityManager() {
|
||||
this.close()
|
||||
serviceHub.withEntityManager {
|
||||
this.metamodel
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -46,38 +47,61 @@ class RestrictedEntityManagerFlowTest {
|
||||
class TestJoinTransactionMethodIsBlocked : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
serviceHub.withEntityManager() {
|
||||
serviceHub.withEntityManager {
|
||||
this.joinTransaction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
fun init() {
|
||||
mockNetwork = MockNetwork(MockNetworkParameters())
|
||||
aliceNode = mockNetwork.createPartyNode(CordaX500Name("Alice", "London", "GB"))
|
||||
}
|
||||
|
||||
@After
|
||||
fun done() {
|
||||
mockNetwork.stopNodes()
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun testIfItIsRestrictedConnection() {
|
||||
fun `restricted connection is returned from ServiceHub#withEntityManager`() {
|
||||
mockNetwork = MockNetwork(MockNetworkParameters(listOf(enclosedCordapp().copy(targetPlatformVersion = PLATFORM_VERSION))))
|
||||
aliceNode = mockNetwork.createPartyNode(CordaX500Name("Alice", "London", "GB"))
|
||||
assertTrue { aliceNode.startFlow(TestIfItIsRestrictedEntityManager()).get() }
|
||||
mockNetwork.runNetwork()
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun testMethodsAreBlocked() {
|
||||
fun `restricted methods are blocked when the target platform is the current corda version`() {
|
||||
mockNetwork = MockNetwork(MockNetworkParameters(listOf(enclosedCordapp().copy(targetPlatformVersion = PLATFORM_VERSION))))
|
||||
aliceNode = mockNetwork.createPartyNode(CordaX500Name("Alice", "London", "GB"))
|
||||
Assertions.assertThatExceptionOfType(UnsupportedOperationException::class.java)
|
||||
.isThrownBy { aliceNode.startFlow(TestCloseMethodIsBlocked()).getOrThrow() }
|
||||
.withMessageContaining("This method cannot be called via ServiceHub.withEntityManager.")
|
||||
.isThrownBy { aliceNode.startFlow(TestGetMetamodelMethodIsBlocked()).getOrThrow() }
|
||||
.withMessageContaining("ServiceHub.withEntityManager.getMetamodel is restricted and cannot be called")
|
||||
|
||||
Assertions.assertThatExceptionOfType(UnsupportedOperationException::class.java)
|
||||
.isThrownBy { aliceNode.startFlow(TestJoinTransactionMethodIsBlocked()).getOrThrow() }
|
||||
.withMessageContaining("This method cannot be called via ServiceHub.withEntityManager.")
|
||||
.withMessageContaining("ServiceHub.withEntityManager.joinTransaction is restricted and cannot be called")
|
||||
|
||||
mockNetwork.runNetwork()
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `restricted methods are blocked when the target platform is 7`() {
|
||||
mockNetwork = MockNetwork(MockNetworkParameters(listOf(enclosedCordapp().copy(targetPlatformVersion = 7))))
|
||||
aliceNode = mockNetwork.createPartyNode(CordaX500Name("Alice", "London", "GB"))
|
||||
Assertions.assertThatExceptionOfType(UnsupportedOperationException::class.java)
|
||||
.isThrownBy { aliceNode.startFlow(TestGetMetamodelMethodIsBlocked()).getOrThrow() }
|
||||
.withMessageContaining("ServiceHub.withEntityManager.getMetamodel is restricted and cannot be called")
|
||||
|
||||
Assertions.assertThatExceptionOfType(UnsupportedOperationException::class.java)
|
||||
.isThrownBy { aliceNode.startFlow(TestJoinTransactionMethodIsBlocked()).getOrThrow() }
|
||||
.withMessageContaining("ServiceHub.withEntityManager.joinTransaction is restricted and cannot be called")
|
||||
|
||||
mockNetwork.runNetwork()
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `restricted methods are not blocked when the target platform is 6`() {
|
||||
mockNetwork = MockNetwork(MockNetworkParameters(listOf(enclosedCordapp().copy(targetPlatformVersion = 6))))
|
||||
aliceNode = mockNetwork.createPartyNode(CordaX500Name("Alice", "London", "GB"))
|
||||
aliceNode.startFlow(TestGetMetamodelMethodIsBlocked()).getOrThrow()
|
||||
aliceNode.startFlow(TestJoinTransactionMethodIsBlocked()).getOrThrow()
|
||||
|
||||
mockNetwork.runNetwork()
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.corda.nodeapi.internal.persistence
|
||||
|
||||
import net.corda.core.node.ServiceHub
|
||||
import java.sql.Connection
|
||||
import java.sql.Savepoint
|
||||
import java.util.concurrent.Executor
|
||||
@ -8,73 +9,73 @@ import java.util.concurrent.Executor
|
||||
* A delegate of [Connection] which disallows some operations.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
class RestrictedConnection(private val delegate : Connection) : Connection by delegate {
|
||||
class RestrictedConnection(private val delegate: Connection, private val serviceHub: ServiceHub) : Connection by delegate {
|
||||
|
||||
override fun abort(executor: Executor?) {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.")
|
||||
restrictDatabaseOperationFromJdbcSession("abort", serviceHub) { delegate.abort(executor) }
|
||||
}
|
||||
|
||||
override fun clearWarnings() {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.")
|
||||
restrictDatabaseOperationFromJdbcSession("clearWarnings", serviceHub) { delegate.clearWarnings() }
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.")
|
||||
restrictDatabaseOperationFromJdbcSession("close", serviceHub) { delegate.close() }
|
||||
}
|
||||
|
||||
override fun commit() {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.")
|
||||
restrictDatabaseOperationFromJdbcSession("commit", serviceHub) { delegate.commit() }
|
||||
}
|
||||
|
||||
override fun setSavepoint(): Savepoint? {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.")
|
||||
return restrictDatabaseOperationFromJdbcSession("setSavepoint", serviceHub) { delegate.setSavepoint() }
|
||||
}
|
||||
|
||||
override fun setSavepoint(name : String?): Savepoint? {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.")
|
||||
override fun setSavepoint(name: String?): Savepoint? {
|
||||
return restrictDatabaseOperationFromJdbcSession("setSavepoint", serviceHub) { delegate.setSavepoint(name) }
|
||||
}
|
||||
|
||||
override fun releaseSavepoint(savepoint: Savepoint?) {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.")
|
||||
restrictDatabaseOperationFromJdbcSession("releaseSavepoint", serviceHub) { delegate.releaseSavepoint(savepoint) }
|
||||
}
|
||||
|
||||
override fun rollback() {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.")
|
||||
restrictDatabaseOperationFromJdbcSession("rollback", serviceHub) { delegate.rollback() }
|
||||
}
|
||||
|
||||
override fun rollback(savepoint: Savepoint?) {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.")
|
||||
restrictDatabaseOperationFromJdbcSession("rollback", serviceHub) { delegate.rollback(savepoint) }
|
||||
}
|
||||
|
||||
override fun setCatalog(catalog : String?) {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.")
|
||||
override fun setCatalog(catalog: String?) {
|
||||
restrictDatabaseOperationFromJdbcSession("setCatalog", serviceHub) { delegate.catalog = catalog }
|
||||
}
|
||||
|
||||
override fun setTransactionIsolation(level: Int) {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.")
|
||||
restrictDatabaseOperationFromJdbcSession("setTransactionIsolation", serviceHub) { delegate.transactionIsolation = level }
|
||||
}
|
||||
|
||||
override fun setTypeMap(map: MutableMap<String, Class<*>>?) {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.")
|
||||
restrictDatabaseOperationFromJdbcSession("setTypeMap", serviceHub) { delegate.typeMap = map }
|
||||
}
|
||||
|
||||
override fun setHoldability(holdability: Int) {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.")
|
||||
restrictDatabaseOperationFromJdbcSession("setHoldability", serviceHub) { delegate.holdability = holdability }
|
||||
}
|
||||
|
||||
override fun setSchema(schema: String?) {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.")
|
||||
restrictDatabaseOperationFromJdbcSession("setSchema", serviceHub) { delegate.schema = schema }
|
||||
}
|
||||
|
||||
override fun setNetworkTimeout(executor: Executor?, milliseconds: Int) {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.")
|
||||
restrictDatabaseOperationFromJdbcSession("setNetworkTimeout", serviceHub) { delegate.setNetworkTimeout(executor, milliseconds) }
|
||||
}
|
||||
|
||||
override fun setAutoCommit(autoCommit: Boolean) {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.")
|
||||
restrictDatabaseOperationFromJdbcSession("setAutoCommit", serviceHub) { delegate.autoCommit = autoCommit }
|
||||
}
|
||||
|
||||
override fun setReadOnly(readOnly: Boolean) {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.jdbcSession.")
|
||||
restrictDatabaseOperationFromJdbcSession("setReadOnly", serviceHub) { delegate.isReadOnly = readOnly }
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package net.corda.nodeapi.internal.persistence
|
||||
|
||||
import net.corda.core.internal.PlatformVersionSwitches.RESTRICTED_DATABASE_OPERATIONS
|
||||
import net.corda.core.internal.warnOnce
|
||||
import net.corda.core.node.ServiceHub
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
private val log = LoggerFactory.getLogger("RestrictedDatabaseOperations")
|
||||
|
||||
internal inline fun <T> restrictDatabaseOperationFromJdbcSession(method: String, serviceHub: ServiceHub, operation: () -> T): T {
|
||||
return restrictDatabaseOperation("ServiceHub.jdbcSession.$method", serviceHub, operation)
|
||||
}
|
||||
|
||||
internal inline fun <T> restrictDatabaseOperationFromEntityManager(method: String, serviceHub: ServiceHub, operation: () -> T): T {
|
||||
return restrictDatabaseOperation("ServiceHub.withEntityManager.$method", serviceHub, operation)
|
||||
}
|
||||
|
||||
internal inline fun <T> restrictDatabaseOperation(method: String, serviceHub: ServiceHub, operation: () -> T): T {
|
||||
return if (serviceHub.getAppContext().cordapp.targetPlatformVersion >= RESTRICTED_DATABASE_OPERATIONS) {
|
||||
throw UnsupportedOperationException("$method is restricted and cannot be called")
|
||||
} else {
|
||||
log.warnOnce(
|
||||
"$method should not be called, as manipulating database transactions and connections breaks the Corda flow state machine in " +
|
||||
"ways that only become evident in failure scenarios. Purely for API backwards compatibility reasons, the prior " +
|
||||
"behaviour is continued for target platform versions less than $RESTRICTED_DATABASE_OPERATIONS. You should evolve " +
|
||||
"the CorDapp away from using these problematic APIs as soon as possible. For target platform version of " +
|
||||
"$RESTRICTED_DATABASE_OPERATIONS or above, an exception will be thrown instead."
|
||||
)
|
||||
operation()
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package net.corda.nodeapi.internal.persistence
|
||||
|
||||
import net.corda.core.node.ServiceHub
|
||||
import javax.persistence.EntityManager
|
||||
import javax.persistence.EntityTransaction
|
||||
import javax.persistence.LockModeType
|
||||
@ -8,56 +9,59 @@ import javax.persistence.metamodel.Metamodel
|
||||
/**
|
||||
* A delegate of [EntityManager] which disallows some operations.
|
||||
*/
|
||||
class RestrictedEntityManager(private val delegate: EntityManager) : EntityManager by delegate {
|
||||
class RestrictedEntityManager(private val delegate: EntityManager, private val serviceHub: ServiceHub) : EntityManager by delegate {
|
||||
|
||||
override fun getTransaction(): EntityTransaction {
|
||||
return RestrictedEntityTransaction(delegate.transaction)
|
||||
return RestrictedEntityTransaction(delegate.transaction, serviceHub)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.withEntityManager.")
|
||||
restrictDatabaseOperationFromEntityManager("close", serviceHub) { delegate.close() }
|
||||
}
|
||||
|
||||
override fun <T : Any?> unwrap(cls: Class<T>?): T {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.withEntityManager.")
|
||||
return restrictDatabaseOperationFromEntityManager("unwrap", serviceHub) { delegate.unwrap(cls) }
|
||||
}
|
||||
|
||||
override fun getDelegate(): Any {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.withEntityManager.")
|
||||
return restrictDatabaseOperationFromEntityManager("getDelegate", serviceHub) { delegate.delegate }
|
||||
}
|
||||
|
||||
override fun getMetamodel(): Metamodel? {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.withEntityManager.")
|
||||
return restrictDatabaseOperationFromEntityManager("getMetamodel", serviceHub) { delegate.metamodel }
|
||||
}
|
||||
|
||||
override fun joinTransaction() {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.withEntityManager.")
|
||||
restrictDatabaseOperationFromEntityManager("joinTransaction", serviceHub) { delegate.joinTransaction() }
|
||||
}
|
||||
|
||||
override fun lock(entity: Any?, lockMode: LockModeType?) {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.withEntityManager.")
|
||||
restrictDatabaseOperationFromEntityManager("lock", serviceHub) { delegate.lock(entity, lockMode) }
|
||||
}
|
||||
|
||||
override fun lock(entity: Any?, lockMode: LockModeType?, properties: MutableMap<String, Any>?) {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.withEntityManager.")
|
||||
restrictDatabaseOperationFromEntityManager("lock", serviceHub) { delegate.lock(entity, lockMode, properties) }
|
||||
}
|
||||
|
||||
override fun setProperty(propertyName: String?, value: Any?) {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.withEntityManager.")
|
||||
restrictDatabaseOperationFromEntityManager("lock", serviceHub) { delegate.setProperty(propertyName, value) }
|
||||
}
|
||||
}
|
||||
|
||||
class RestrictedEntityTransaction(private val delegate: EntityTransaction) : EntityTransaction by delegate {
|
||||
class RestrictedEntityTransaction(
|
||||
private val delegate: EntityTransaction,
|
||||
private val serviceHub: ServiceHub
|
||||
) : EntityTransaction by delegate {
|
||||
|
||||
override fun rollback() {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.withEntityManager.")
|
||||
restrictDatabaseOperationFromEntityManager("EntityTransaction.rollback", serviceHub) { delegate.rollback() }
|
||||
}
|
||||
|
||||
override fun commit() {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.withEntityManager.")
|
||||
restrictDatabaseOperationFromEntityManager("EntityTransaction.commit", serviceHub) { delegate.commit() }
|
||||
}
|
||||
|
||||
override fun begin() {
|
||||
throw UnsupportedOperationException("This method cannot be called via ServiceHub.withEntityManager.")
|
||||
restrictDatabaseOperationFromEntityManager("EntityTransaction.begin", serviceHub) { delegate.begin() }
|
||||
}
|
||||
}
|
@ -30,7 +30,7 @@ import java.security.PublicKey
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
import java.util.Collections
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KMutableProperty
|
||||
@ -509,6 +509,7 @@ class ThrowableSerializer<T>(kryo: Kryo, type: Class<T>) : Serializer<Throwable>
|
||||
@ThreadSafe
|
||||
@SuppressWarnings("ALL")
|
||||
object LazyMappedListSerializer : Serializer<List<*>>() {
|
||||
override fun write(kryo: Kryo, output: Output, obj: List<*>) = kryo.writeClassAndObject(output, obj.toList())
|
||||
override fun read(kryo: Kryo, input: Input, type: Class<List<*>>) = kryo.readClassAndObject(input) as List<*>
|
||||
// Using a MutableList so that Kryo will always write an instance of java.util.ArrayList.
|
||||
override fun write(kryo: Kryo, output: Output, obj: List<*>) = kryo.writeClassAndObject(output, obj.toMutableList())
|
||||
override fun read(kryo: Kryo, input: Input, type: Class<List<*>>) = kryo.readClassAndObject(input) as? List<*>
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ object KryoCheckpointSerializer : CheckpointSerializer {
|
||||
*/
|
||||
private fun getInputClassForCustomSerializer(classLoader: ClassLoader, customSerializer: CustomSerializerCheckpointAdaptor<*, *>): Class<*> {
|
||||
val typeNameWithoutGenerics = customSerializer.cordappType.typeName.substringBefore('<')
|
||||
return classLoader.loadClass(typeNameWithoutGenerics)
|
||||
return Class.forName(typeNameWithoutGenerics, false, classLoader)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,104 +1,337 @@
|
||||
package net.corda.nodeapi.internal.persistence
|
||||
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.cordapp.Cordapp
|
||||
import net.corda.core.cordapp.CordappContext
|
||||
import net.corda.core.internal.PLATFORM_VERSION
|
||||
import net.corda.core.node.ServiceHub
|
||||
import org.junit.Test
|
||||
import java.sql.Connection
|
||||
import java.sql.Savepoint
|
||||
|
||||
class RestrictedConnectionTest {
|
||||
|
||||
private val connection : Connection = mock()
|
||||
private val savePoint : Savepoint = mock()
|
||||
private val restrictedConnection : RestrictedConnection = RestrictedConnection(connection)
|
||||
private val connection: Connection = mock()
|
||||
private val savePoint: Savepoint = mock()
|
||||
private val cordapp = mock<Cordapp>()
|
||||
private val cordappContext = CordappContext.create(cordapp, null, javaClass.classLoader, mock())
|
||||
private val serviceHub = mock<ServiceHub>().apply {
|
||||
whenever(getAppContext()).thenReturn(cordappContext)
|
||||
}
|
||||
private val restrictedConnection: RestrictedConnection = RestrictedConnection(connection, serviceHub)
|
||||
|
||||
companion object {
|
||||
private const val TEST_STRING : String = "test"
|
||||
private const val TEST_INT : Int = 1
|
||||
private const val TEST_STRING: String = "test"
|
||||
private const val TEST_INT: Int = 1
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testAbort() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `abort with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedConnection.abort { println("I'm just an executor for this test...") }
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testClearWarnings() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `clearWarnings with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedConnection.clearWarnings()
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testClose() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `close with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedConnection.close()
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testCommit() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `commit with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedConnection.commit()
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testSetSavepoint() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setSavepoint with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedConnection.setSavepoint()
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testSetSavepointWithName() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setSavepoint with name with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedConnection.setSavepoint(TEST_STRING)
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testReleaseSavepoint() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `releaseSavepoint with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedConnection.releaseSavepoint(savePoint)
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testRollback() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `rollback with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedConnection.rollback()
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testRollbackWithSavepoint() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `rollbackWithSavepoint with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedConnection.rollback(savePoint)
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testSetCatalog() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setCatalog with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedConnection.catalog = TEST_STRING
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testSetTransactionIsolation() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setTransactionIsolation with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedConnection.transactionIsolation = TEST_INT
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testSetTypeMap() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setTypeMap with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
val map: MutableMap<String, Class<*>> = mutableMapOf()
|
||||
restrictedConnection.typeMap = map
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testSetHoldability() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setHoldability with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedConnection.holdability = TEST_INT
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testSetSchema() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setSchema with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedConnection.schema = TEST_STRING
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testSetNetworkTimeout() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setNetworkTimeout with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedConnection.setNetworkTimeout({ println("I'm just an executor for this test...") }, TEST_INT)
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testSetAutoCommit() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setAutoCommit with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedConnection.autoCommit = true
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testSetReadOnly() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setReadOnly with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedConnection.isReadOnly = true
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `abort with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedConnection.abort { println("I'm just an executor for this test...") }
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `clearWarnings with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedConnection.clearWarnings()
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `close with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedConnection.close()
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `commit with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedConnection.commit()
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setSavepoint with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedConnection.setSavepoint()
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setSavepoint with name with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedConnection.setSavepoint(TEST_STRING)
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `releaseSavepoint with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedConnection.releaseSavepoint(savePoint)
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `rollback with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedConnection.rollback()
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `rollbackWithSavepoint with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedConnection.rollback(savePoint)
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setCatalog with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedConnection.catalog = TEST_STRING
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setTransactionIsolation with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedConnection.transactionIsolation = TEST_INT
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setTypeMap with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
val map: MutableMap<String, Class<*>> = mutableMapOf()
|
||||
restrictedConnection.typeMap = map
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setHoldability with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedConnection.holdability = TEST_INT
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setSchema with target platform version of current 7 unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedConnection.schema = TEST_STRING
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setNetworkTimeout with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedConnection.setNetworkTimeout({ println("I'm just an executor for this test...") }, TEST_INT)
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setAutoCommit with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedConnection.autoCommit = true
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setReadOnly with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedConnection.isReadOnly = true
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `abort with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedConnection.abort { println("I'm just an executor for this test...") }
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `clearWarnings with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedConnection.clearWarnings()
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `close with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedConnection.close()
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `commit with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedConnection.commit()
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `setSavepoint with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedConnection.setSavepoint()
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `setSavepoint with name with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedConnection.setSavepoint(TEST_STRING)
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `releaseSavepoint with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedConnection.releaseSavepoint(savePoint)
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `rollback with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedConnection.rollback()
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `rollbackWithSavepoint with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedConnection.rollback(savePoint)
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `setCatalog with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedConnection.catalog = TEST_STRING
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `setTransactionIsolation with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedConnection.transactionIsolation = TEST_INT
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `setTypeMap with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
val map: MutableMap<String, Class<*>> = mutableMapOf()
|
||||
restrictedConnection.typeMap = map
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `setHoldability with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedConnection.holdability = TEST_INT
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `setSchema with target platform version of current 6 unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedConnection.schema = TEST_STRING
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `setNetworkTimeout with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedConnection.setNetworkTimeout({ println("I'm just an executor for this test...") }, TEST_INT)
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `setAutoCommit with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedConnection.autoCommit = true
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `setReadOnly with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedConnection.isReadOnly = true
|
||||
}
|
||||
}
|
@ -3,6 +3,10 @@ package net.corda.nodeapi.internal.persistence
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.cordapp.Cordapp
|
||||
import net.corda.core.cordapp.CordappContext
|
||||
import net.corda.core.internal.PLATFORM_VERSION
|
||||
import net.corda.core.node.ServiceHub
|
||||
import org.junit.Test
|
||||
import javax.persistence.EntityManager
|
||||
import javax.persistence.EntityTransaction
|
||||
@ -12,47 +16,160 @@ import kotlin.test.assertTrue
|
||||
class RestrictedEntityManagerTest {
|
||||
private val entitymanager = mock<EntityManager>()
|
||||
private val transaction = mock<EntityTransaction>()
|
||||
private val restrictedEntityManager = RestrictedEntityManager(entitymanager)
|
||||
private val cordapp = mock<Cordapp>()
|
||||
private val cordappContext = CordappContext.create(cordapp, null, javaClass.classLoader, mock())
|
||||
private val serviceHub = mock<ServiceHub>().apply {
|
||||
whenever(getAppContext()).thenReturn(cordappContext)
|
||||
}
|
||||
private val restrictedEntityManager = RestrictedEntityManager(entitymanager, serviceHub)
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testClose() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `close with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedEntityManager.close()
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun testClear() {
|
||||
fun `clear with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedEntityManager.clear()
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testGetMetaModel() {
|
||||
restrictedEntityManager.getMetamodel()
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `getMetaModel with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedEntityManager.metamodel
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun testGetTransaction() {
|
||||
fun `getTransaction with target platform version of current corda version executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
whenever(entitymanager.transaction).doReturn(transaction)
|
||||
assertTrue(restrictedEntityManager.transaction is RestrictedEntityTransaction)
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testJoinTransaction() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `joinTransaction with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedEntityManager.joinTransaction()
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testLockWithTwoParameters() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `lock with two parameters with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedEntityManager.lock(Object(), LockModeType.OPTIMISTIC)
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testLockWithThreeParameters() {
|
||||
val map: MutableMap<String,Any> = mutableMapOf()
|
||||
restrictedEntityManager.lock(Object(), LockModeType.OPTIMISTIC,map)
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `lock with three parameters with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
val map: MutableMap<String, Any> = mutableMapOf()
|
||||
restrictedEntityManager.lock(Object(), LockModeType.OPTIMISTIC, map)
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout=300_000)
|
||||
fun testSetProperty() {
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setProperty with target platform version of current corda version throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(PLATFORM_VERSION)
|
||||
restrictedEntityManager.setProperty("number", 12)
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `close with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedEntityManager.close()
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `clear with target platform version of 7 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedEntityManager.clear()
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `getMetaModel with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedEntityManager.metamodel
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `getTransaction with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
whenever(entitymanager.transaction).doReturn(transaction)
|
||||
assertTrue(restrictedEntityManager.transaction is RestrictedEntityTransaction)
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `joinTransaction with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedEntityManager.joinTransaction()
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `lock with two parameters with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedEntityManager.lock(Object(), LockModeType.OPTIMISTIC)
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `lock with three parameters with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
val map: MutableMap<String, Any> = mutableMapOf()
|
||||
restrictedEntityManager.lock(Object(), LockModeType.OPTIMISTIC, map)
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException::class, timeout = 300_000)
|
||||
fun `setProperty with target platform version of 7 throws unsupported exception`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(7)
|
||||
restrictedEntityManager.setProperty("number", 12)
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `close with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedEntityManager.close()
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `clear with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedEntityManager.clear()
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `getMetaModel with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedEntityManager.metamodel
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `getTransaction with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
whenever(entitymanager.transaction).doReturn(transaction)
|
||||
assertTrue(restrictedEntityManager.transaction is RestrictedEntityTransaction)
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `joinTransaction with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedEntityManager.joinTransaction()
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `lock with two parameters with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedEntityManager.lock(Object(), LockModeType.OPTIMISTIC)
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `lock with three parameters with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
val map: MutableMap<String, Any> = mutableMapOf()
|
||||
restrictedEntityManager.lock(Object(), LockModeType.OPTIMISTIC, map)
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `setProperty with target platform version of 6 executes successfully`() {
|
||||
whenever(cordapp.targetPlatformVersion).thenReturn(6)
|
||||
restrictedEntityManager.setProperty("number", 12)
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package net.corda.node.djvm
|
||||
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.BrokenAttachmentException
|
||||
import net.corda.core.contracts.ContractAttachment
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.Party
|
||||
import java.io.InputStream
|
||||
@ -16,6 +17,12 @@ private const val ID_IDX = 2
|
||||
private const val ATTACHMENT_IDX = 3
|
||||
private const val STREAMER_IDX = 4
|
||||
|
||||
private const val CONTRACT_IDX = 5
|
||||
private const val ADDITIONAL_CONTRACT_IDX = 6
|
||||
private const val UPLOADER_IDX = 7
|
||||
private const val CONTRACT_SIGNER_KEYS_IDX = 8
|
||||
private const val VERSION_IDX = 9
|
||||
|
||||
class AttachmentBuilder : Function<Array<Any>?, List<Attachment>?> {
|
||||
private val attachments = mutableListOf<Attachment>()
|
||||
|
||||
@ -28,17 +35,30 @@ class AttachmentBuilder : Function<Array<Any>?, List<Attachment>?> {
|
||||
}
|
||||
|
||||
override fun apply(inputs: Array<Any>?): List<Attachment>? {
|
||||
@Suppress("unchecked_cast")
|
||||
return if (inputs == null) {
|
||||
unmodifiable(attachments)
|
||||
} else {
|
||||
@Suppress("unchecked_cast")
|
||||
attachments.add(SandboxAttachment(
|
||||
var attachment: Attachment = SandboxAttachment(
|
||||
signerKeys = inputs[SIGNER_KEYS_IDX] as List<PublicKey>,
|
||||
size = inputs[SIZE_IDX] as Int,
|
||||
id = inputs[ID_IDX] as SecureHash,
|
||||
attachment = inputs[ATTACHMENT_IDX],
|
||||
streamer = inputs[STREAMER_IDX] as Function<in Any, out InputStream>
|
||||
))
|
||||
)
|
||||
|
||||
if (inputs.size > VERSION_IDX) {
|
||||
attachment = ContractAttachment.create(
|
||||
attachment = attachment,
|
||||
contract = inputs[CONTRACT_IDX] as String,
|
||||
additionalContracts = (inputs[ADDITIONAL_CONTRACT_IDX] as Array<String>).toSet(),
|
||||
uploader = inputs[UPLOADER_IDX] as? String,
|
||||
signerKeys = inputs[CONTRACT_SIGNER_KEYS_IDX] as List<PublicKey>,
|
||||
version = inputs[VERSION_IDX] as Int
|
||||
)
|
||||
}
|
||||
|
||||
attachments.add(attachment)
|
||||
null
|
||||
}
|
||||
}
|
||||
@ -47,7 +67,7 @@ class AttachmentBuilder : Function<Array<Any>?, List<Attachment>?> {
|
||||
/**
|
||||
* This represents an [Attachment] from within the sandbox.
|
||||
*/
|
||||
class SandboxAttachment(
|
||||
private class SandboxAttachment(
|
||||
override val signerKeys: List<PublicKey>,
|
||||
override val size: Int,
|
||||
override val id: SecureHash,
|
||||
|
@ -5,35 +5,43 @@ import net.corda.core.contracts.CommandWithParties
|
||||
import net.corda.core.internal.lazyMapped
|
||||
import java.security.PublicKey
|
||||
import java.util.function.Function
|
||||
import java.util.function.Supplier
|
||||
|
||||
class CommandBuilder : Function<Array<Any?>, List<CommandWithParties<CommandData>>> {
|
||||
class CommandBuilder : Function<Array<Any?>, Supplier<List<CommandWithParties<CommandData>>>> {
|
||||
@Suppress("unchecked_cast")
|
||||
override fun apply(inputs: Array<Any?>): List<CommandWithParties<CommandData>> {
|
||||
val signers = inputs[0] as? List<List<PublicKey>> ?: emptyList()
|
||||
val commandsData = inputs[1] as? List<CommandData> ?: emptyList()
|
||||
override fun apply(inputs: Array<Any?>): Supplier<List<CommandWithParties<CommandData>>> {
|
||||
val signersProvider = inputs[0] as? Supplier<List<List<PublicKey>>> ?: Supplier(::emptyList)
|
||||
val commandsDataProvider = inputs[1] as? Supplier<List<CommandData>> ?: Supplier(::emptyList)
|
||||
val partialMerkleLeafIndices = inputs[2] as? IntArray
|
||||
|
||||
/**
|
||||
* This logic has been lovingly reproduced from [net.corda.core.internal.deserialiseCommands].
|
||||
*/
|
||||
return if (partialMerkleLeafIndices != null) {
|
||||
check(commandsData.size <= signers.size) {
|
||||
"Invalid Transaction. Fewer Signers (${signers.size}) than CommandData (${commandsData.size}) objects"
|
||||
}
|
||||
if (partialMerkleLeafIndices.isNotEmpty()) {
|
||||
check(partialMerkleLeafIndices.max()!! < signers.size) {
|
||||
"Invalid Transaction. A command with no corresponding signer detected"
|
||||
return Supplier {
|
||||
val signers = signersProvider.get()
|
||||
val commandsData = commandsDataProvider.get()
|
||||
|
||||
if (partialMerkleLeafIndices != null) {
|
||||
check(commandsData.size <= signers.size) {
|
||||
"Invalid Transaction. Fewer Signers (${signers.size}) than CommandData (${commandsData.size}) objects"
|
||||
}
|
||||
if (partialMerkleLeafIndices.isNotEmpty()) {
|
||||
check(partialMerkleLeafIndices.max()!! < signers.size) {
|
||||
"Invalid Transaction. A command with no corresponding signer detected"
|
||||
}
|
||||
}
|
||||
commandsData.lazyMapped { commandData, index ->
|
||||
// Deprecated signingParties property not supported.
|
||||
CommandWithParties(signers[partialMerkleLeafIndices[index]], emptyList(), commandData)
|
||||
}
|
||||
} else {
|
||||
check(commandsData.size == signers.size) {
|
||||
"Invalid Transaction. Sizes of CommandData (${commandsData.size}) and Signers (${signers.size}) do not match"
|
||||
}
|
||||
commandsData.lazyMapped { commandData, index ->
|
||||
// Deprecated signingParties property not supported.
|
||||
CommandWithParties(signers[index], emptyList(), commandData)
|
||||
}
|
||||
}
|
||||
commandsData.lazyMapped { commandData, index ->
|
||||
CommandWithParties(signers[partialMerkleLeafIndices[index]], emptyList(), commandData)
|
||||
}
|
||||
} else {
|
||||
check(commandsData.size == signers.size) {
|
||||
"Invalid Transaction. Sizes of CommandData (${commandsData.size}) and Signers (${signers.size}) do not match"
|
||||
}
|
||||
commandsData.lazyMapped { commandData, index ->
|
||||
CommandWithParties(signers[index], emptyList(), commandData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,19 +5,22 @@ import net.corda.core.internal.TransactionDeserialisationException
|
||||
import net.corda.core.internal.lazyMapped
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import java.util.function.Function
|
||||
import java.util.function.Supplier
|
||||
|
||||
class ComponentBuilder : Function<Array<Any?>, List<*>> {
|
||||
class ComponentBuilder : Function<Array<Any?>, Supplier<List<*>>> {
|
||||
@Suppress("unchecked_cast", "TooGenericExceptionCaught")
|
||||
override fun apply(inputs: Array<Any?>): List<*> {
|
||||
override fun apply(inputs: Array<Any?>): Supplier<List<*>> {
|
||||
val deserializer = inputs[0] as Function<in Any?, out Any?>
|
||||
val groupType = inputs[1] as ComponentGroupEnum
|
||||
val components = (inputs[2] as Array<ByteArray>).map(::OpaqueBytes)
|
||||
|
||||
return components.lazyMapped { component, index ->
|
||||
try {
|
||||
deserializer.apply(component.bytes)
|
||||
} catch (e: Exception) {
|
||||
throw TransactionDeserialisationException(groupType, index, e)
|
||||
return Supplier {
|
||||
components.lazyMapped { component, index ->
|
||||
try {
|
||||
deserializer.apply(component.bytes)
|
||||
} catch (e: Exception) {
|
||||
throw TransactionDeserialisationException(groupType, index, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,54 +0,0 @@
|
||||
@file:JvmName("LtxConstants")
|
||||
package net.corda.node.djvm
|
||||
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.CommandData
|
||||
import net.corda.core.contracts.CommandWithParties
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.PrivacySalt
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TimeWindow
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.crypto.DigestService
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import java.util.function.Function
|
||||
|
||||
private const val TX_INPUTS = 0
|
||||
private const val TX_OUTPUTS = 1
|
||||
private const val TX_COMMANDS = 2
|
||||
private const val TX_ATTACHMENTS = 3
|
||||
private const val TX_ID = 4
|
||||
private const val TX_NOTARY = 5
|
||||
private const val TX_TIME_WINDOW = 6
|
||||
private const val TX_PRIVACY_SALT = 7
|
||||
private const val TX_NETWORK_PARAMETERS = 8
|
||||
private const val TX_REFERENCES = 9
|
||||
private const val TX_DIGEST_SERVICE = 10
|
||||
|
||||
class LtxFactory : Function<Array<out Any?>, LedgerTransaction> {
|
||||
|
||||
@Suppress("unchecked_cast")
|
||||
override fun apply(txArgs: Array<out Any?>): LedgerTransaction {
|
||||
return LedgerTransaction.createForSandbox(
|
||||
inputs = (txArgs[TX_INPUTS] as Array<Array<out Any?>>).map { it.toStateAndRef() },
|
||||
outputs = (txArgs[TX_OUTPUTS] as? List<TransactionState<ContractState>>) ?: emptyList(),
|
||||
commands = (txArgs[TX_COMMANDS] as? List<CommandWithParties<CommandData>>) ?: emptyList(),
|
||||
attachments = (txArgs[TX_ATTACHMENTS] as? List<Attachment>) ?: emptyList(),
|
||||
id = txArgs[TX_ID] as SecureHash,
|
||||
notary = txArgs[TX_NOTARY] as? Party,
|
||||
timeWindow = txArgs[TX_TIME_WINDOW] as? TimeWindow,
|
||||
privacySalt = txArgs[TX_PRIVACY_SALT] as PrivacySalt,
|
||||
networkParameters = txArgs[TX_NETWORK_PARAMETERS] as NetworkParameters,
|
||||
references = (txArgs[TX_REFERENCES] as Array<Array<out Any?>>).map { it.toStateAndRef() },
|
||||
digestService = if (txArgs.size > TX_DIGEST_SERVICE) (txArgs[TX_DIGEST_SERVICE] as DigestService) else DigestService.sha2_256
|
||||
)
|
||||
}
|
||||
|
||||
private fun Array<*>.toStateAndRef(): StateAndRef<ContractState> {
|
||||
return StateAndRef(this[0] as TransactionState<*>, this[1] as StateRef)
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
@file:JvmName("LtxTools")
|
||||
package net.corda.node.djvm
|
||||
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.CommandData
|
||||
import net.corda.core.contracts.CommandWithParties
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.PrivacySalt
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TimeWindow
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.crypto.DigestService
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import java.util.function.Function
|
||||
import java.util.function.Supplier
|
||||
|
||||
private const val TX_INPUTS = 0
|
||||
private const val TX_OUTPUTS = 1
|
||||
private const val TX_COMMANDS = 2
|
||||
private const val TX_ATTACHMENTS = 3
|
||||
private const val TX_ID = 4
|
||||
private const val TX_NOTARY = 5
|
||||
private const val TX_TIME_WINDOW = 6
|
||||
private const val TX_PRIVACY_SALT = 7
|
||||
private const val TX_NETWORK_PARAMETERS = 8
|
||||
private const val TX_REFERENCES = 9
|
||||
private const val TX_DIGEST_SERVICE = 10
|
||||
|
||||
class LtxSupplierFactory : Function<Array<out Any?>, Supplier<LedgerTransaction>> {
|
||||
@Suppress("unchecked_cast")
|
||||
override fun apply(txArgs: Array<out Any?>): Supplier<LedgerTransaction> {
|
||||
val inputProvider = (txArgs[TX_INPUTS] as Function<in Any?, Array<Array<out Any?>>>)
|
||||
.andThen(Function(Array<Array<out Any?>>::toContractStatesAndRef))
|
||||
.toSupplier()
|
||||
val outputProvider = txArgs[TX_OUTPUTS] as? Supplier<List<TransactionState<ContractState>>> ?: Supplier(::emptyList)
|
||||
val commandsProvider = txArgs[TX_COMMANDS] as Supplier<List<CommandWithParties<CommandData>>>
|
||||
val referencesProvider = (txArgs[TX_REFERENCES] as Function<in Any?, Array<Array<out Any?>>>)
|
||||
.andThen(Function(Array<Array<out Any?>>::toContractStatesAndRef))
|
||||
.toSupplier()
|
||||
val networkParameters = (txArgs[TX_NETWORK_PARAMETERS] as? NetworkParameters)?.toImmutable()
|
||||
return Supplier {
|
||||
LedgerTransaction.createForContractVerify(
|
||||
inputs = inputProvider.get(),
|
||||
outputs = outputProvider.get(),
|
||||
commands = commandsProvider.get(),
|
||||
attachments = txArgs[TX_ATTACHMENTS] as? List<Attachment> ?: emptyList(),
|
||||
id = txArgs[TX_ID] as SecureHash,
|
||||
notary = txArgs[TX_NOTARY] as? Party,
|
||||
timeWindow = txArgs[TX_TIME_WINDOW] as? TimeWindow,
|
||||
privacySalt = txArgs[TX_PRIVACY_SALT] as PrivacySalt,
|
||||
networkParameters = networkParameters,
|
||||
references = referencesProvider.get(),
|
||||
digestService = txArgs[TX_DIGEST_SERVICE] as DigestService
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> Function<in Any?, T>.toSupplier(): Supplier<T> {
|
||||
return Supplier { apply(null) }
|
||||
}
|
||||
|
||||
private fun Array<Array<out Any?>>.toContractStatesAndRef(): List<StateAndRef<ContractState>> {
|
||||
return map(Array<out Any?>::toStateAndRef)
|
||||
}
|
||||
|
||||
private fun Array<*>.toStateAndRef(): StateAndRef<ContractState> {
|
||||
return StateAndRef(this[0] as TransactionState<*>, this[1] as StateRef)
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package net.corda.serialization.reproduction;
|
||||
|
||||
import com.google.common.io.LineProcessor;
|
||||
import net.corda.client.rpc.CordaRPCClient;
|
||||
import net.corda.core.concurrent.CordaFuture;
|
||||
import net.corda.node.services.Permissions;
|
||||
|
@ -0,0 +1,46 @@
|
||||
package net.corda.contracts.multiple.evil
|
||||
|
||||
import net.corda.contracts.multiple.vulnerable.MutableDataObject
|
||||
import net.corda.contracts.multiple.vulnerable.VulnerablePaymentContract.VulnerablePurchase
|
||||
import net.corda.contracts.multiple.vulnerable.VulnerablePaymentContract.VulnerableState
|
||||
import net.corda.core.contracts.CommandData
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
|
||||
@Suppress("unused")
|
||||
class EvilContract : Contract {
|
||||
override fun verify(tx: LedgerTransaction) {
|
||||
val vulnerableStates = tx.outputsOfType(VulnerableState::class.java)
|
||||
val vulnerablePurchases = tx.commandsOfType(VulnerablePurchase::class.java)
|
||||
|
||||
val addExtras = tx.commandsOfType(AddExtra::class.java)
|
||||
addExtras.forEach { extra ->
|
||||
val extraValue = extra.value.payment.value
|
||||
|
||||
// And our extra value to every vulnerable output state.
|
||||
vulnerableStates.forEach { state ->
|
||||
state.data?.also { data ->
|
||||
data.value += extraValue
|
||||
}
|
||||
}
|
||||
|
||||
// Add our extra value to every vulnerable command too.
|
||||
vulnerablePurchases.forEach { purchase ->
|
||||
purchase.value.payment.value += extraValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EvilState(val owner: AbstractParty) : ContractState {
|
||||
override val participants: List<AbstractParty> = listOf(owner)
|
||||
|
||||
@Override
|
||||
override fun toString(): String {
|
||||
return "Money For Nothing!"
|
||||
}
|
||||
}
|
||||
|
||||
class AddExtra(val payment: MutableDataObject) : CommandData
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package net.corda.contracts.multiple.vulnerable
|
||||
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
|
||||
@CordaSerializable
|
||||
data class MutableDataObject(var value: Long) : Comparable<MutableDataObject> {
|
||||
override fun toString(): String {
|
||||
return "$value data points"
|
||||
}
|
||||
|
||||
override fun compareTo(other: MutableDataObject): Int {
|
||||
return value.compareTo(other.value)
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package net.corda.contracts.multiple.vulnerable
|
||||
|
||||
import net.corda.core.contracts.CommandData
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.requireThat
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
|
||||
@Suppress("unused")
|
||||
class VulnerablePaymentContract : Contract {
|
||||
companion object {
|
||||
const val BASE_PAYMENT = 2000L
|
||||
}
|
||||
|
||||
override fun verify(tx: LedgerTransaction) {
|
||||
val states = tx.outputsOfType<VulnerableState>()
|
||||
requireThat {
|
||||
"Requires at least one data state" using states.isNotEmpty()
|
||||
}
|
||||
val purchases = tx.commandsOfType<VulnerablePurchase>()
|
||||
requireThat {
|
||||
"Requires at least one purchase" using purchases.isNotEmpty()
|
||||
}
|
||||
for (purchase in purchases) {
|
||||
val payment = purchase.value.payment
|
||||
requireThat {
|
||||
"Purchase payment of $payment should be at least $BASE_PAYMENT" using (payment.value >= BASE_PAYMENT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VulnerableState(val owner: AbstractParty, val data: MutableDataObject?) : ContractState {
|
||||
override val participants: List<AbstractParty> = listOf(owner)
|
||||
|
||||
@Override
|
||||
override fun toString(): String {
|
||||
return data.toString()
|
||||
}
|
||||
}
|
||||
|
||||
class VulnerablePurchase(val payment: MutableDataObject) : CommandData
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
package net.corda.contracts.mutator
|
||||
|
||||
import net.corda.core.contracts.CommandData
|
||||
import net.corda.core.contracts.CommandWithParties
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.contracts.requireSingleCommand
|
||||
import net.corda.core.contracts.requireThat
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.internal.Verifier
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
|
||||
class MutatorContract : Contract {
|
||||
override fun verify(tx: LedgerTransaction) {
|
||||
tx.transform { componentGroups, serializedInputs, serializedReferences ->
|
||||
requireThat {
|
||||
"component groups are protected" using componentGroups.isImmutableAnd(isEmpty = true)
|
||||
"serialized inputs are protected" using serializedInputs.isImmutableAnd(isEmpty = true)
|
||||
"serialized references are protected" using serializedReferences.isImmutableAnd(isEmpty = true)
|
||||
}
|
||||
}
|
||||
|
||||
requireThat {
|
||||
"Cannot add/remove inputs" using tx.inputs.isImmutable()
|
||||
"Cannot add/remove outputs" using failToMutateOutputs(tx)
|
||||
"Cannot add/remove commands" using failToMutateCommands(tx)
|
||||
"Cannot add/remove references" using tx.references.isImmutable()
|
||||
"Cannot add/remove attachments" using tx.attachments.isImmutableAnd(isEmpty = false)
|
||||
"Cannot specialise transaction" using failToSpecialise(tx)
|
||||
}
|
||||
|
||||
requireNotNull(tx.networkParameters).also { networkParameters ->
|
||||
requireThat {
|
||||
"Cannot add/remove notaries" using networkParameters.notaries.isImmutableAnd(isEmpty = false)
|
||||
"Cannot add/remove package ownerships" using networkParameters.packageOwnership.isImmutable()
|
||||
"Cannot add/remove whitelisted contracts" using networkParameters.whitelistedContractImplementations.isImmutable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<*>.isImmutableAnd(isEmpty: Boolean): Boolean {
|
||||
return isImmutable() && (this.isEmpty() == isEmpty)
|
||||
}
|
||||
|
||||
private fun List<*>.isImmutable(): Boolean {
|
||||
return try {
|
||||
@Suppress("platform_class_mapped_to_kotlin")
|
||||
(this as java.util.List<*>).clear()
|
||||
false
|
||||
} catch (e: UnsupportedOperationException) {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun failToMutateOutputs(tx: LedgerTransaction): Boolean {
|
||||
val output = tx.outputsOfType<MutateState>().single()
|
||||
val mutableOutputs = tx.outputs as MutableList<in TransactionState<ContractState>>
|
||||
return try {
|
||||
mutableOutputs += TransactionState(MutateState(output.owner), MutatorContract::class.java.name, tx.notary!!, 0)
|
||||
false
|
||||
} catch (e: UnsupportedOperationException) {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun failToMutateCommands(tx: LedgerTransaction): Boolean {
|
||||
val mutate = tx.commands.requireSingleCommand<MutateCommand>()
|
||||
val mutableCommands = tx.commands as MutableList<in CommandWithParties<CommandData>>
|
||||
return try {
|
||||
mutableCommands += CommandWithParties(mutate.signers, emptyList(), MutateCommand())
|
||||
false
|
||||
} catch (e: UnsupportedOperationException) {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun Map<*, *>.isImmutable(): Boolean {
|
||||
return try {
|
||||
@Suppress("platform_class_mapped_to_kotlin")
|
||||
(this as java.util.Map<*, *>).clear()
|
||||
false
|
||||
} catch (e: UnsupportedOperationException) {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun failToSpecialise(ltx: LedgerTransaction): Boolean {
|
||||
return try {
|
||||
ltx.specialise(::ExtraSpecialise)
|
||||
false
|
||||
} catch (e: IllegalStateException) {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private class ExtraSpecialise(private val ltx: LedgerTransaction, private val ctx: SerializationContext) : Verifier {
|
||||
override fun verify() {
|
||||
ltx.inputStates.forEach(::println)
|
||||
println(ctx.deserializationClassLoader)
|
||||
}
|
||||
}
|
||||
|
||||
class MutateState(val owner: AbstractParty) : ContractState {
|
||||
override val participants: List<AbstractParty> = listOf(owner)
|
||||
|
||||
@Override
|
||||
override fun toString(): String {
|
||||
return "All change!"
|
||||
}
|
||||
}
|
||||
|
||||
class MutateCommand : CommandData
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package net.corda.flows.multiple.evil
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.multiple.evil.EvilContract.EvilState
|
||||
import net.corda.contracts.multiple.evil.EvilContract.AddExtra
|
||||
import net.corda.contracts.multiple.vulnerable.MutableDataObject
|
||||
import net.corda.contracts.multiple.vulnerable.VulnerablePaymentContract.VulnerablePurchase
|
||||
import net.corda.contracts.multiple.vulnerable.VulnerablePaymentContract.VulnerableState
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
|
||||
@StartableByRPC
|
||||
class EvilFlow(
|
||||
private val purchase: MutableDataObject
|
||||
) : FlowLogic<SecureHash>() {
|
||||
private companion object {
|
||||
private val NOTHING = MutableDataObject(0)
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
override fun call(): SecureHash {
|
||||
val notary = serviceHub.networkMapCache.notaryIdentities[0]
|
||||
val stx = serviceHub.signInitialTransaction(
|
||||
TransactionBuilder(notary)
|
||||
// Add Evil objects first, so that Corda will verify EvilContract first.
|
||||
.addCommand(Command(AddExtra(purchase), ourIdentity.owningKey))
|
||||
.addOutputState(EvilState(ourIdentity))
|
||||
|
||||
// Now add the VulnerablePaymentContract objects with NO PAYMENT!
|
||||
.addCommand(Command(VulnerablePurchase(NOTHING), ourIdentity.owningKey))
|
||||
.addOutputState(VulnerableState(ourIdentity, NOTHING))
|
||||
)
|
||||
stx.verify(serviceHub, checkSufficientSignatures = false)
|
||||
return stx.id
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package net.corda.flows.mutator
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.mutator.MutatorContract.MutateCommand
|
||||
import net.corda.contracts.mutator.MutatorContract.MutateState
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
|
||||
@StartableByRPC
|
||||
class MutatorFlow : FlowLogic<SecureHash>() {
|
||||
@Suspendable
|
||||
override fun call(): SecureHash {
|
||||
val notary = serviceHub.networkMapCache.notaryIdentities[0]
|
||||
val stx = serviceHub.signInitialTransaction(
|
||||
TransactionBuilder(notary)
|
||||
// Create some content for the LedgerTransaction.
|
||||
.addOutputState(MutateState(ourIdentity))
|
||||
.addCommand(Command(MutateCommand(), ourIdentity.owningKey))
|
||||
)
|
||||
stx.verify(serviceHub, checkSufficientSignatures = false)
|
||||
return stx.id
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package net.corda.node
|
||||
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.flows.CashIssueAndPaymentFlow
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.driver.internal.incrementalPortAllocation
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import net.corda.testing.node.internal.findCordapp
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.assertDoesNotThrow
|
||||
|
||||
/**
|
||||
* Execute a flow with sub-flows, including the finality flow.
|
||||
* This operation should checkpoint, and have its checkpoint restored.
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
class CashIssueAndPaymentTest {
|
||||
companion object {
|
||||
private val logger = loggerFor<CashIssueAndPaymentTest>()
|
||||
|
||||
private val configOverrides = mapOf(NodeConfiguration::reloadCheckpointAfterSuspend.name to true)
|
||||
private val CASH_AMOUNT = 500.DOLLARS
|
||||
|
||||
fun parametersFor(runInProcess: Boolean = false): DriverParameters {
|
||||
return DriverParameters(
|
||||
systemProperties = mapOf("co.paralleluniverse.fibers.verifyInstrumentation" to "false"),
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = runInProcess,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, startInProcess = runInProcess, validating = true)),
|
||||
notaryCustomOverrides = configOverrides,
|
||||
cordappsForAllNodes = listOf(
|
||||
findCordapp("net.corda.finance.contracts"),
|
||||
findCordapp("net.corda.finance.workflows")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `test can issue cash`() {
|
||||
driver(parametersFor()) {
|
||||
val alice = startNode(providedName = ALICE_NAME, customOverrides = configOverrides).getOrThrow()
|
||||
val aliceParty = alice.nodeInfo.singleIdentity()
|
||||
val notaryParty = notaryHandles.single().identity
|
||||
val result = assertDoesNotThrow {
|
||||
alice.rpc.startFlow(::CashIssueAndPaymentFlow,
|
||||
CASH_AMOUNT,
|
||||
OpaqueBytes.of(0x01),
|
||||
aliceParty,
|
||||
false,
|
||||
notaryParty
|
||||
).use { flowHandle ->
|
||||
flowHandle.returnValue.getOrThrow()
|
||||
}
|
||||
}
|
||||
logger.info("TXN={}, recipient={}", result.stx, result.recipient)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package net.corda.node
|
||||
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.flows.mutator.MutatorFlow
|
||||
import net.corda.node.services.Permissions
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.driver.internal.incrementalPortAllocation
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.internal.cordappWithPackages
|
||||
import org.junit.Test
|
||||
|
||||
class ContractCannotMutateTransactionTest {
|
||||
companion object {
|
||||
private val logger = loggerFor<ContractCannotMutateTransactionTest>()
|
||||
private val user = User("u", "p", setOf(Permissions.all()))
|
||||
private val mutatorFlowCorDapp = cordappWithPackages("net.corda.flows.mutator").signed()
|
||||
private val mutatorContractCorDapp = cordappWithPackages("net.corda.contracts.mutator").signed()
|
||||
|
||||
fun driverParameters(runInProcess: Boolean = false): DriverParameters {
|
||||
return DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = runInProcess,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, startInProcess = runInProcess, validating = true)),
|
||||
cordappsForAllNodes = listOf(mutatorContractCorDapp, mutatorFlowCorDapp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun testContractCannotModifyTransaction() {
|
||||
driver(driverParameters()) {
|
||||
val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
|
||||
val txID = CordaRPCClient(hostAndPort = alice.rpcAddress)
|
||||
.start(user.username, user.password)
|
||||
.use { client ->
|
||||
client.proxy.startFlow(::MutatorFlow).returnValue.getOrThrow()
|
||||
}
|
||||
logger.info("TX-ID: {}", txID)
|
||||
}
|
||||
}
|
||||
}
|
@ -35,11 +35,11 @@ class ContractWithCordappFixupTest {
|
||||
val dependentContractCorDapp = cordappWithPackages("net.corda.contracts.fixup.dependent").signed()
|
||||
val standaloneContractCorDapp = cordappWithPackages("net.corda.contracts.fixup.standalone").signed()
|
||||
|
||||
fun driverParameters(cordapps: List<TestCordapp>): DriverParameters {
|
||||
fun driverParameters(cordapps: List<TestCordapp>, runInProcess: Boolean = false): DriverParameters {
|
||||
return DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = false,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
|
||||
startNodesInProcess = runInProcess,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, startInProcess = runInProcess, validating = true)),
|
||||
cordappsForAllNodes = cordapps,
|
||||
systemProperties = mapOf("net.corda.transactionbuilder.missingclass.disabled" to true.toString())
|
||||
)
|
||||
|
@ -46,7 +46,7 @@ class ContractWithCustomSerializerTest(private val runInProcess: Boolean) {
|
||||
driver(DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = runInProcess,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, startInProcess = runInProcess, validating = true)),
|
||||
cordappsForAllNodes = listOf(
|
||||
cordappWithPackages("net.corda.flows.serialization.custom").signed(),
|
||||
cordappWithPackages("net.corda.contracts.serialization.custom").signed()
|
||||
|
@ -31,11 +31,11 @@ class ContractWithGenericTypeTest {
|
||||
@JvmField
|
||||
val user = User("u", "p", setOf(Permissions.all()))
|
||||
|
||||
fun parameters(): DriverParameters {
|
||||
fun parameters(runInProcess: Boolean = false): DriverParameters {
|
||||
return DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = false,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
|
||||
startNodesInProcess = runInProcess,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, startInProcess = runInProcess, validating = true)),
|
||||
cordappsForAllNodes = listOf(
|
||||
cordappWithPackages("net.corda.flows.serialization.generics").signed(),
|
||||
cordappWithPackages("net.corda.contracts.serialization.generics").signed()
|
||||
|
@ -45,7 +45,7 @@ class ContractWithMissingCustomSerializerTest(private val runInProcess: Boolean)
|
||||
return DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = runInProcess,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, startInProcess = runInProcess, validating = true)),
|
||||
cordappsForAllNodes = cordapps
|
||||
)
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class ContractWithSerializationWhitelistTest(private val runInProcess: Boolean)
|
||||
return DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = runInProcess,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, startInProcess = runInProcess, validating = true)),
|
||||
cordappsForAllNodes = listOf(contractCordapp, workflowCordapp)
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,60 @@
|
||||
package net.corda.node
|
||||
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.contracts.multiple.vulnerable.MutableDataObject
|
||||
import net.corda.core.contracts.TransactionVerificationException.ContractRejection
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.flows.multiple.evil.EvilFlow
|
||||
import net.corda.node.services.Permissions
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.driver.internal.incrementalPortAllocation
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.internal.cordappWithPackages
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class EvilContractCannotModifyStatesTest {
|
||||
companion object {
|
||||
private val user = User("u", "p", setOf(Permissions.all()))
|
||||
private val evilFlowCorDapp = cordappWithPackages("net.corda.flows.multiple.evil").signed()
|
||||
private val evilContractCorDapp = cordappWithPackages("net.corda.contracts.multiple.evil").signed()
|
||||
private val vulnerableContractCorDapp = cordappWithPackages("net.corda.contracts.multiple.vulnerable").signed()
|
||||
|
||||
private val NOTHING = MutableDataObject(0)
|
||||
|
||||
fun driverParameters(runInProcess: Boolean): DriverParameters {
|
||||
return DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = runInProcess,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, startInProcess = runInProcess, validating = true)),
|
||||
cordappsForAllNodes = listOf(
|
||||
vulnerableContractCorDapp,
|
||||
evilContractCorDapp,
|
||||
evilFlowCorDapp
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun testContractThatTriesToModifyStates() {
|
||||
val evilData = MutableDataObject(5000)
|
||||
driver(driverParameters(runInProcess = false)) {
|
||||
val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
|
||||
val ex = assertFailsWith<ContractRejection> {
|
||||
CordaRPCClient(hostAndPort = alice.rpcAddress)
|
||||
.start(user.username, user.password)
|
||||
.use { client ->
|
||||
client.proxy.startFlow(::EvilFlow, evilData).returnValue.getOrThrow()
|
||||
}
|
||||
}
|
||||
assertThat(ex).hasMessageContaining("Purchase payment of $NOTHING should be at least ")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,10 @@
|
||||
package net.corda.node.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.esotericsoftware.kryo.Kryo
|
||||
import com.esotericsoftware.kryo.KryoSerializable
|
||||
import com.esotericsoftware.kryo.io.Input
|
||||
import com.esotericsoftware.kryo.io.Output
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.client.rpc.PermissionException
|
||||
import net.corda.core.CordaRuntimeException
|
||||
@ -11,10 +15,14 @@ import net.corda.core.flows.ResultSerializationException
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.internal.concurrent.OpenFuture
|
||||
import net.corda.core.internal.concurrent.doOnError
|
||||
import net.corda.core.internal.concurrent.openFuture
|
||||
import net.corda.core.messaging.FlowHandleWithClientId
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.messaging.startFlowWithClientId
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SerializationDefaults
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.node.services.Permissions
|
||||
@ -23,9 +31,12 @@ import net.corda.nodeapi.exceptions.RejectedCommandException
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.NodeHandle
|
||||
import net.corda.testing.driver.NodeParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.internal.enclosedCordapp
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import rx.Observable
|
||||
@ -33,6 +44,7 @@ import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.test.assertEquals
|
||||
@ -498,6 +510,124 @@ class FlowWithClientIdTest {
|
||||
}
|
||||
}
|
||||
|
||||
// This test is not very realistic because the scenario it happens under is also not very realistic.
|
||||
@Test(timeout = 300_000)
|
||||
fun `flow started with client id that fails before its first checkpoint that contains an unserializable argument will be persited as FAILED`() {
|
||||
val clientId = UUID.randomUUID().toString()
|
||||
driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = listOf(enclosedCordapp()))) {
|
||||
val nodeA = startNode(NodeParameters(ALICE_NAME)).getOrThrow()
|
||||
val flowHandle = nodeA.rpc.startFlowWithClientId(clientId, ::QuickFailingFlow, LazyUnserializableObject())
|
||||
val reattachedFlowHandle = nodeA.rpc.reattachFlowWithClientId<Int>(clientId)
|
||||
|
||||
assertThatExceptionOfType(CordaRuntimeException::class.java).isThrownBy {
|
||||
flowHandle.returnValue.getOrThrow(20.seconds)
|
||||
}.withMessage("I have failed quickly")
|
||||
|
||||
assertThatExceptionOfType(CordaRuntimeException::class.java).isThrownBy {
|
||||
reattachedFlowHandle?.returnValue?.getOrThrow()
|
||||
}.withMessage("I have failed quickly")
|
||||
|
||||
assertTrue(nodeA.hasStatus(flowHandle.id, Checkpoint.FlowStatus.FAILED))
|
||||
val arguments = nodeA.rpc.startFlow(::GetFlowInitialArgumentsFromMetadata, flowHandle.id).returnValue.getOrThrow(20.seconds)
|
||||
assertEquals(arguments.size, 1)
|
||||
assertTrue(arguments.single() is LazyUnserializableObject)
|
||||
}
|
||||
}
|
||||
|
||||
// This test has been added to replicate the exact scenario a user experienced.
|
||||
@Test(timeout = 300_000)
|
||||
fun `flow started with client id that fails before its first checkpoint with subflow'd flow will be persited as FAILED`() {
|
||||
val clientId = UUID.randomUUID().toString()
|
||||
driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = listOf(enclosedCordapp()))) {
|
||||
val nodeA = startNode(NodeParameters(ALICE_NAME)).getOrThrow()
|
||||
val flowHandle = nodeA.rpc.startFlowWithClientId(clientId, ::PassedInFailingFlow, SuperQuickFailingFlow())
|
||||
val reattachedFlowHandle = nodeA.rpc.reattachFlowWithClientId<Int>(clientId)
|
||||
|
||||
assertThatExceptionOfType(CordaRuntimeException::class.java).isThrownBy {
|
||||
flowHandle.returnValue.getOrThrow(20.seconds)
|
||||
}.withMessage("I have failed quickly")
|
||||
|
||||
assertThatExceptionOfType(CordaRuntimeException::class.java).isThrownBy {
|
||||
reattachedFlowHandle?.returnValue?.getOrThrow()
|
||||
}.withMessage("I have failed quickly")
|
||||
|
||||
assertTrue(nodeA.hasStatus(flowHandle.id, Checkpoint.FlowStatus.FAILED))
|
||||
val arguments = nodeA.rpc.startFlow(::GetFlowInitialArgumentsFromMetadata, flowHandle.id).returnValue.getOrThrow(20.seconds)
|
||||
assertEquals(arguments.size, 1)
|
||||
assertTrue(arguments.single() is SuperQuickFailingFlow)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `flow started with client id that fails can use doOnError to process the exception`() {
|
||||
val clientId = UUID.randomUUID().toString()
|
||||
driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = listOf(enclosedCordapp()))) {
|
||||
val nodeA = startNode(NodeParameters(ALICE_NAME)).getOrThrow()
|
||||
val flowHandle = nodeA.rpc.startFlowWithClientId(clientId, ::SuperQuickFailingFlow)
|
||||
val reattachedFlowHandle = nodeA.rpc.reattachFlowWithClientId<Int>(clientId)
|
||||
|
||||
val lock = CountDownLatch(1)
|
||||
val reattachedLock = CountDownLatch(1)
|
||||
|
||||
flowHandle.returnValue.doOnError {
|
||||
lock.countDown()
|
||||
}
|
||||
|
||||
reattachedFlowHandle?.returnValue?.doOnError {
|
||||
reattachedLock.countDown()
|
||||
}
|
||||
|
||||
assertTrue(lock.await(20, TimeUnit.SECONDS))
|
||||
assertTrue(reattachedLock.await(20, TimeUnit.SECONDS))
|
||||
assertTrue(flowHandle.returnValue.isDone)
|
||||
assertTrue(reattachedFlowHandle!!.returnValue.isDone)
|
||||
}
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
@StartableByRPC
|
||||
internal class QuickFailingFlow(private val lazyUnserializableObject: LazyUnserializableObject) : FlowLogic<Int>() {
|
||||
|
||||
@Suspendable
|
||||
override fun call(): Int {
|
||||
lazyUnserializableObject.prop = UnserializableObject()
|
||||
throw CordaRuntimeException("I have failed quickly")
|
||||
}
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
class LazyUnserializableObject(var prop: UnserializableObject? = null)
|
||||
|
||||
@CordaSerializable
|
||||
class UnserializableObject : KryoSerializable {
|
||||
override fun write(kryo: Kryo?, output: Output?) {
|
||||
throw IllegalStateException("Cannot be serialized")
|
||||
}
|
||||
|
||||
override fun read(kryo: Kryo?, input: Input?) {
|
||||
throw IllegalStateException("Cannot be read")
|
||||
}
|
||||
}
|
||||
|
||||
@StartableByRPC
|
||||
internal class PassedInFailingFlow(private val flow: SuperQuickFailingFlow) : FlowLogic<Int>() {
|
||||
@Suspendable
|
||||
override fun call(): Int {
|
||||
return subFlow(flow)
|
||||
}
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
@StartableByRPC
|
||||
internal class SuperQuickFailingFlow : FlowLogic<Int>() {
|
||||
|
||||
@Suspendable
|
||||
override fun call(): Int {
|
||||
throw CordaRuntimeException("I have failed quickly")
|
||||
}
|
||||
}
|
||||
|
||||
@StartableByRPC
|
||||
internal class ResultFlow<A>(private val result: A) : FlowLogic<A>() {
|
||||
companion object {
|
||||
@ -568,6 +698,24 @@ class FlowWithClientIdTest {
|
||||
}
|
||||
}
|
||||
|
||||
@StartableByRPC
|
||||
internal class GetFlowInitialArgumentsFromMetadata(private val id: StateMachineRunId) : FlowLogic<List<Any?>>() {
|
||||
@Suspendable
|
||||
override fun call(): List<Any?> {
|
||||
val argumentBytes = serviceHub.jdbcSession().prepareStatement("select flow_parameters from node_flow_metadata where flow_id = ?")
|
||||
.apply {
|
||||
setString(1, id.uuid.toString())
|
||||
}
|
||||
.use { ps ->
|
||||
ps.executeQuery().use { rs ->
|
||||
rs.next()
|
||||
rs.getBytes(1)
|
||||
}
|
||||
}
|
||||
return argumentBytes.deserialize(context = SerializationDefaults.STORAGE_CONTEXT)
|
||||
}
|
||||
}
|
||||
|
||||
internal class UnserializableException(
|
||||
val unserializableObject: BrokenMap<Unit, Unit> = BrokenMap()
|
||||
) : CordaRuntimeException("123")
|
||||
|
@ -7,6 +7,7 @@ import net.corda.core.utilities.loggerFor
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.flows.CashIssueAndPaymentFlow
|
||||
import net.corda.node.DeterministicSourcesRule
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.core.singleIdentity
|
||||
@ -22,20 +23,21 @@ import org.junit.jupiter.api.assertDoesNotThrow
|
||||
@Suppress("FunctionName")
|
||||
class DeterministicCashIssueAndPaymentTest {
|
||||
companion object {
|
||||
val logger = loggerFor<DeterministicCashIssueAndPaymentTest>()
|
||||
private val logger = loggerFor<DeterministicCashIssueAndPaymentTest>()
|
||||
|
||||
private val configOverrides = mapOf(NodeConfiguration::reloadCheckpointAfterSuspend.name to true)
|
||||
private val CASH_AMOUNT = 500.DOLLARS
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val djvmSources = DeterministicSourcesRule()
|
||||
|
||||
@JvmField
|
||||
val CASH_AMOUNT = 500.DOLLARS
|
||||
|
||||
fun parametersFor(djvmSources: DeterministicSourcesRule): DriverParameters {
|
||||
fun parametersFor(djvmSources: DeterministicSourcesRule, runInProcess: Boolean = false): DriverParameters {
|
||||
return DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = false,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
|
||||
startNodesInProcess = runInProcess,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, startInProcess = runInProcess, validating = true)),
|
||||
notaryCustomOverrides = configOverrides,
|
||||
cordappsForAllNodes = listOf(
|
||||
findCordapp("net.corda.finance.contracts"),
|
||||
findCordapp("net.corda.finance.workflows")
|
||||
@ -50,7 +52,7 @@ class DeterministicCashIssueAndPaymentTest {
|
||||
fun `test DJVM can issue cash`() {
|
||||
val reference = OpaqueBytes.of(0x01)
|
||||
driver(parametersFor(djvmSources)) {
|
||||
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
|
||||
val alice = startNode(providedName = ALICE_NAME, customOverrides = configOverrides).getOrThrow()
|
||||
val aliceParty = alice.nodeInfo.singleIdentity()
|
||||
val notaryParty = notaryHandles.single().identity
|
||||
val txId = assertDoesNotThrow {
|
||||
@ -60,7 +62,9 @@ class DeterministicCashIssueAndPaymentTest {
|
||||
aliceParty,
|
||||
false,
|
||||
notaryParty
|
||||
).returnValue.getOrThrow()
|
||||
).use { flowHandle ->
|
||||
flowHandle.returnValue.getOrThrow()
|
||||
}
|
||||
}
|
||||
logger.info("TX-ID: {}", txId)
|
||||
}
|
||||
|
@ -0,0 +1,55 @@
|
||||
package net.corda.node.services
|
||||
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.flows.mutator.MutatorFlow
|
||||
import net.corda.node.DeterministicSourcesRule
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.driver.internal.incrementalPortAllocation
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.internal.cordappWithPackages
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
|
||||
class DeterministicContractCannotMutateTransactionTest {
|
||||
companion object {
|
||||
private val logger = loggerFor<DeterministicContractCannotMutateTransactionTest>()
|
||||
private val user = User("u", "p", setOf(Permissions.all()))
|
||||
private val mutatorFlowCorDapp = cordappWithPackages("net.corda.flows.mutator").signed()
|
||||
private val mutatorContractCorDapp = cordappWithPackages("net.corda.contracts.mutator").signed()
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val djvmSources = DeterministicSourcesRule()
|
||||
|
||||
fun driverParameters(runInProcess: Boolean = false): DriverParameters {
|
||||
return DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = runInProcess,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, startInProcess = runInProcess, validating = true)),
|
||||
cordappsForAllNodes = listOf(mutatorContractCorDapp, mutatorFlowCorDapp),
|
||||
djvmBootstrapSource = djvmSources.bootstrap,
|
||||
djvmCordaSource = djvmSources.corda
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun testContractCannotModifyTransaction() {
|
||||
driver(driverParameters()) {
|
||||
val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
|
||||
val txID = CordaRPCClient(hostAndPort = alice.rpcAddress)
|
||||
.start(user.username, user.password)
|
||||
.use { client ->
|
||||
client.proxy.startFlow(::MutatorFlow).returnValue.getOrThrow()
|
||||
}
|
||||
logger.info("TX-ID: {}", txID)
|
||||
}
|
||||
}
|
||||
}
|
@ -32,11 +32,11 @@ class DeterministicContractCryptoTest {
|
||||
@JvmField
|
||||
val djvmSources = DeterministicSourcesRule()
|
||||
|
||||
fun parametersFor(djvmSources: DeterministicSourcesRule): DriverParameters {
|
||||
fun parametersFor(djvmSources: DeterministicSourcesRule, runInProcess: Boolean = false): DriverParameters {
|
||||
return DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = false,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
|
||||
startNodesInProcess = runInProcess,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, startInProcess = runInProcess, validating = true)),
|
||||
cordappsForAllNodes = listOf(
|
||||
cordappWithPackages("net.corda.flows.djvm.crypto"),
|
||||
CustomCordapp(
|
||||
|
@ -41,11 +41,11 @@ class DeterministicContractWithCustomSerializerTest {
|
||||
@JvmField
|
||||
val contractCordapp = cordappWithPackages("net.corda.contracts.serialization.custom").signed()
|
||||
|
||||
fun parametersFor(djvmSources: DeterministicSourcesRule, vararg cordapps: TestCordapp): DriverParameters {
|
||||
fun parametersFor(djvmSources: DeterministicSourcesRule, cordapps: List<TestCordapp>, runInProcess: Boolean = false): DriverParameters {
|
||||
return DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = false,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
|
||||
startNodesInProcess = runInProcess,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, startInProcess = runInProcess, validating = true)),
|
||||
cordappsForAllNodes = cordapps.toList(),
|
||||
djvmBootstrapSource = djvmSources.bootstrap,
|
||||
djvmCordaSource = djvmSources.corda
|
||||
@ -61,7 +61,7 @@ class DeterministicContractWithCustomSerializerTest {
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `test DJVM can verify using custom serializer`() {
|
||||
driver(parametersFor(djvmSources, flowCordapp, contractCordapp)) {
|
||||
driver(parametersFor(djvmSources, listOf(flowCordapp, contractCordapp))) {
|
||||
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
|
||||
val txId = assertDoesNotThrow {
|
||||
alice.rpc.startFlow(::CustomSerializerFlow, Currantsy(GOOD_CURRANTS))
|
||||
@ -73,7 +73,7 @@ class DeterministicContractWithCustomSerializerTest {
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `test DJVM can fail verify using custom serializer`() {
|
||||
driver(parametersFor(djvmSources, flowCordapp, contractCordapp)) {
|
||||
driver(parametersFor(djvmSources, listOf(flowCordapp, contractCordapp))) {
|
||||
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
|
||||
val currantsy = Currantsy(BAD_CURRANTS)
|
||||
val ex = assertThrows<DeterministicVerificationException> {
|
||||
|
@ -36,11 +36,11 @@ class DeterministicContractWithGenericTypeTest {
|
||||
@JvmField
|
||||
val djvmSources = DeterministicSourcesRule()
|
||||
|
||||
fun parameters(): DriverParameters {
|
||||
fun parameters(runInProcess: Boolean = false): DriverParameters {
|
||||
return DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = false,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
|
||||
startNodesInProcess = runInProcess,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, startInProcess = runInProcess, validating = true)),
|
||||
cordappsForAllNodes = listOf(
|
||||
cordappWithPackages("net.corda.flows.serialization.generics").signed(),
|
||||
cordappWithPackages("net.corda.contracts.serialization.generics").signed()
|
||||
|
@ -41,11 +41,11 @@ class DeterministicContractWithSerializationWhitelistTest {
|
||||
@JvmField
|
||||
val contractCordapp = cordappWithPackages("net.corda.contracts.djvm.whitelist").signed()
|
||||
|
||||
fun parametersFor(djvmSources: DeterministicSourcesRule, vararg cordapps: TestCordapp): DriverParameters {
|
||||
fun parametersFor(djvmSources: DeterministicSourcesRule, cordapps: List<TestCordapp>, runInProcess: Boolean = false): DriverParameters {
|
||||
return DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = false,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
|
||||
startNodesInProcess = runInProcess,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, startInProcess = runInProcess, validating = true)),
|
||||
cordappsForAllNodes = cordapps.toList(),
|
||||
djvmBootstrapSource = djvmSources.bootstrap,
|
||||
djvmCordaSource = djvmSources.corda
|
||||
@ -61,7 +61,7 @@ class DeterministicContractWithSerializationWhitelistTest {
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `test DJVM can verify using whitelist`() {
|
||||
driver(parametersFor(djvmSources, flowCordapp, contractCordapp)) {
|
||||
driver(parametersFor(djvmSources, listOf(flowCordapp, contractCordapp))) {
|
||||
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
|
||||
val txId = assertDoesNotThrow {
|
||||
alice.rpc.startFlow(::DeterministicWhitelistFlow, WhitelistData(GOOD_VALUE))
|
||||
@ -73,7 +73,7 @@ class DeterministicContractWithSerializationWhitelistTest {
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `test DJVM can fail verify using whitelist`() {
|
||||
driver(parametersFor(djvmSources, flowCordapp, contractCordapp)) {
|
||||
driver(parametersFor(djvmSources, listOf(flowCordapp, contractCordapp))) {
|
||||
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
|
||||
val badData = WhitelistData(BAD_VALUE)
|
||||
val ex = assertThrows<DeterministicVerificationException> {
|
||||
|
@ -0,0 +1,71 @@
|
||||
package net.corda.node.services
|
||||
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.contracts.multiple.vulnerable.MutableDataObject
|
||||
import net.corda.contracts.multiple.vulnerable.VulnerablePaymentContract
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.flows.multiple.evil.EvilFlow
|
||||
import net.corda.node.DeterministicSourcesRule
|
||||
import net.corda.node.internal.djvm.DeterministicVerificationException
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.driver.internal.incrementalPortAllocation
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.internal.cordappWithPackages
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class DeterministicEvilContractCannotModifyStatesTest {
|
||||
companion object {
|
||||
private val user = User("u", "p", setOf(Permissions.all()))
|
||||
private val evilFlowCorDapp = cordappWithPackages("net.corda.flows.multiple.evil").signed()
|
||||
private val evilContractCorDapp = cordappWithPackages("net.corda.contracts.multiple.evil").signed()
|
||||
private val vulnerableContractCorDapp = cordappWithPackages("net.corda.contracts.multiple.vulnerable").signed()
|
||||
|
||||
private val NOTHING = MutableDataObject(0)
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val djvmSources = DeterministicSourcesRule()
|
||||
|
||||
fun driverParameters(runInProcess: Boolean = false): DriverParameters {
|
||||
return DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = runInProcess,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, startInProcess = runInProcess, validating = true)),
|
||||
cordappsForAllNodes = listOf(
|
||||
vulnerableContractCorDapp,
|
||||
evilContractCorDapp,
|
||||
evilFlowCorDapp
|
||||
),
|
||||
djvmBootstrapSource = djvmSources.bootstrap,
|
||||
djvmCordaSource = djvmSources.corda
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun testContractThatTriesToModifyStates() {
|
||||
val evilData = MutableDataObject(5000)
|
||||
driver(driverParameters()) {
|
||||
val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
|
||||
val ex = assertFailsWith<DeterministicVerificationException> {
|
||||
CordaRPCClient(hostAndPort = alice.rpcAddress)
|
||||
.start(user.username, user.password)
|
||||
.use { client ->
|
||||
client.proxy.startFlow(::EvilFlow, evilData).returnValue.getOrThrow()
|
||||
}
|
||||
}
|
||||
assertThat(ex)
|
||||
.hasMessageStartingWith("sandbox.net.corda.core.contracts.TransactionVerificationException\$ContractRejection -> ")
|
||||
.hasMessageContaining(" Contract verification failed: Failed requirement: Purchase payment of $NOTHING should be at least ")
|
||||
.hasMessageContaining(", contract: sandbox.${VulnerablePaymentContract::class.java.name}, ")
|
||||
}
|
||||
}
|
||||
}
|
@ -35,11 +35,11 @@ class NonDeterministicContractVerifyTest {
|
||||
@JvmField
|
||||
val djvmSources = DeterministicSourcesRule()
|
||||
|
||||
fun parametersFor(djvmSources: DeterministicSourcesRule): DriverParameters {
|
||||
fun parametersFor(djvmSources: DeterministicSourcesRule, runInProcess: Boolean = false): DriverParameters {
|
||||
return DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = false,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
|
||||
startNodesInProcess = runInProcess,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, startInProcess = runInProcess, validating = true)),
|
||||
cordappsForAllNodes = listOf(
|
||||
cordappWithPackages("net.corda.flows.djvm.broken"),
|
||||
CustomCordapp(
|
||||
|
@ -5,7 +5,6 @@ import net.corda.contracts.djvm.attachment.SandboxAttachmentContract.ExtractFile
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.djvm.code.asResourcePath
|
||||
import net.corda.flows.djvm.attachment.SandboxAttachmentFlow
|
||||
import net.corda.node.DeterministicSourcesRule
|
||||
import net.corda.node.internal.djvm.DeterministicVerificationException
|
||||
@ -32,11 +31,11 @@ class SandboxAttachmentsTest {
|
||||
@JvmField
|
||||
val djvmSources = DeterministicSourcesRule()
|
||||
|
||||
fun parametersFor(djvmSources: DeterministicSourcesRule): DriverParameters {
|
||||
fun parametersFor(djvmSources: DeterministicSourcesRule, runInProcess: Boolean = false): DriverParameters {
|
||||
return DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = false,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
|
||||
startNodesInProcess = runInProcess,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, startInProcess = runInProcess, validating = true)),
|
||||
cordappsForAllNodes = listOf(
|
||||
cordappWithPackages("net.corda.flows.djvm.attachment"),
|
||||
CustomCordapp(
|
||||
@ -52,7 +51,7 @@ class SandboxAttachmentsTest {
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `test attachment accessible within sandbox`() {
|
||||
val extractFile = ExtractFile(SandboxAttachmentContract::class.java.name.asResourcePath + ".class")
|
||||
val extractFile = ExtractFile(SandboxAttachmentContract::class.java.name.replace('.', '/') + ".class")
|
||||
driver(parametersFor(djvmSources)) {
|
||||
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
|
||||
val txId = assertDoesNotThrow {
|
||||
|
@ -1162,7 +1162,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
/**
|
||||
* Exposes the database connection as a [RestrictedConnection] to the users.
|
||||
*/
|
||||
override fun jdbcSession(): Connection = RestrictedConnection(database.createSession())
|
||||
override fun jdbcSession(): Connection = RestrictedConnection(database.createSession(), services)
|
||||
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
override fun <T : Any?> withEntityManager(block: EntityManager.() -> T): T {
|
||||
@ -1172,7 +1172,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
withSavePoint { savepoint ->
|
||||
// Restrict what entity manager they can use inside the block
|
||||
try {
|
||||
block(RestrictedEntityManager(manager)).also {
|
||||
block(RestrictedEntityManager(manager, services)).also {
|
||||
if (!manager.transaction.rollbackOnly) {
|
||||
manager.flush()
|
||||
} else {
|
||||
|
@ -14,7 +14,7 @@ inline fun <reified A : Annotation> Class<*>.requireAnnotation(): A {
|
||||
|
||||
fun scanForCustomSerializationScheme(className: String, classLoader: ClassLoader) : SerializationScheme {
|
||||
val schemaClass = try {
|
||||
classLoader.loadClass(className)
|
||||
Class.forName(className, false, classLoader)
|
||||
} catch (exception: ClassNotFoundException) {
|
||||
throw ConfigurationException("$className was declared as a custom serialization scheme but could not be found.")
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.node.internal.djvm
|
||||
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.ContractAttachment
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.djvm.rewiring.SandboxClassLoader
|
||||
import net.corda.node.djvm.AttachmentBuilder
|
||||
@ -19,14 +20,30 @@ class AttachmentFactory(
|
||||
fun toSandbox(attachments: List<Attachment>): Any? {
|
||||
val builder = taskFactory.apply(AttachmentBuilder::class.java)
|
||||
for (attachment in attachments) {
|
||||
builder.apply(arrayOf(
|
||||
serializer.deserialize(attachment.signerKeys.serialize()),
|
||||
sandboxBasicInput.apply(attachment.size),
|
||||
serializer.deserialize(attachment.id.serialize()),
|
||||
attachment,
|
||||
sandboxOpenAttachment
|
||||
))
|
||||
builder.apply(generateArgsFor(attachment))
|
||||
}
|
||||
return builder.apply(null)
|
||||
}
|
||||
|
||||
private fun generateArgsFor(attachment: Attachment): Array<Any?> {
|
||||
val signerKeys = serializer.deserialize(attachment.signerKeys.serialize())
|
||||
val id = serializer.deserialize(attachment.id.serialize())
|
||||
val size = sandboxBasicInput.apply(attachment.size)
|
||||
return if (attachment is ContractAttachment) {
|
||||
val underlyingAttachment = attachment.attachment
|
||||
arrayOf(
|
||||
serializer.deserialize(underlyingAttachment.signerKeys.serialize()),
|
||||
size, id,
|
||||
underlyingAttachment,
|
||||
sandboxOpenAttachment,
|
||||
sandboxBasicInput.apply(attachment.contract),
|
||||
sandboxBasicInput.apply(attachment.additionalContracts.toTypedArray()),
|
||||
sandboxBasicInput.apply(attachment.uploader),
|
||||
signerKeys,
|
||||
sandboxBasicInput.apply(attachment.version)
|
||||
)
|
||||
} else {
|
||||
arrayOf(signerKeys, size, id, attachment, sandboxOpenAttachment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,13 +7,14 @@ import net.corda.core.contracts.ComponentGroupEnum.SIGNERS_GROUP
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.contracts.TransactionVerificationException
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.internal.ContractVerifier
|
||||
import net.corda.core.internal.TransactionVerifier
|
||||
import net.corda.core.internal.Verifier
|
||||
import net.corda.core.internal.getNamesOfClassesImplementing
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.djvm.SandboxConfiguration
|
||||
import net.corda.djvm.execution.ExecutionSummary
|
||||
import net.corda.djvm.execution.IsolatedTask
|
||||
@ -21,15 +22,19 @@ import net.corda.djvm.execution.SandboxException
|
||||
import net.corda.djvm.messages.Message
|
||||
import net.corda.djvm.rewiring.SandboxClassLoader
|
||||
import net.corda.djvm.source.ClassSource
|
||||
import net.corda.node.djvm.LtxFactory
|
||||
import net.corda.node.djvm.LtxSupplierFactory
|
||||
import java.util.function.Function
|
||||
import kotlin.collections.LinkedHashSet
|
||||
|
||||
class DeterministicVerifier(
|
||||
ltx: LedgerTransaction,
|
||||
transactionClassLoader: ClassLoader,
|
||||
private val ltx: LedgerTransaction,
|
||||
private val transactionClassLoader: ClassLoader,
|
||||
private val sandboxConfiguration: SandboxConfiguration
|
||||
) : Verifier(ltx, transactionClassLoader) {
|
||||
) : Verifier {
|
||||
private companion object {
|
||||
private val logger = contextLogger()
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the whitelisted classes without using the [java.util.ServiceLoader] mechanism
|
||||
* because the whitelists themselves are untrusted.
|
||||
@ -47,7 +52,7 @@ class DeterministicVerifier(
|
||||
}
|
||||
}
|
||||
|
||||
override fun verifyContracts() {
|
||||
override fun verify() {
|
||||
val customSerializerNames = getNamesOfClassesImplementing(transactionClassLoader, SerializationCustomSerializer::class.java)
|
||||
val serializationWhitelistNames = getSerializationWhitelistNames(transactionClassLoader)
|
||||
val result = IsolatedTask(ltx.id.toString(), sandboxConfiguration).run<Any>(Function { classLoader ->
|
||||
@ -93,14 +98,14 @@ class DeterministicVerifier(
|
||||
val networkingParametersData = ltx.networkParameters?.serialize()
|
||||
val digestServiceData = ltx.digestService.serialize()
|
||||
|
||||
val createSandboxTx = taskFactory.apply(LtxFactory::class.java)
|
||||
val createSandboxTx = taskFactory.apply(LtxSupplierFactory::class.java)
|
||||
createSandboxTx.apply(arrayOf(
|
||||
serializer.deserialize(serializedInputs),
|
||||
classLoader.createForImport(Function { serializer.deserialize(serializedInputs) }),
|
||||
componentFactory.toSandbox(OUTPUTS_GROUP, TransactionState::class.java),
|
||||
CommandFactory(taskFactory).toSandbox(
|
||||
componentFactory.toSandbox(SIGNERS_GROUP, List::class.java),
|
||||
componentFactory.toSandbox(COMMANDS_GROUP, CommandData::class.java),
|
||||
componentFactory.calculateLeafIndicesFor(COMMANDS_GROUP, digestService = ltx.digestService)
|
||||
componentFactory.calculateLeafIndicesFor(COMMANDS_GROUP, ltx.digestService)
|
||||
),
|
||||
attachmentFactory.toSandbox(ltx.attachments),
|
||||
serializer.deserialize(idData),
|
||||
@ -108,12 +113,12 @@ class DeterministicVerifier(
|
||||
serializer.deserialize(timeWindowData),
|
||||
serializer.deserialize(privacySaltData),
|
||||
serializer.deserialize(networkingParametersData),
|
||||
serializer.deserialize(serializedReferences),
|
||||
classLoader.createForImport(Function { serializer.deserialize(serializedReferences) }),
|
||||
serializer.deserialize(digestServiceData)
|
||||
))
|
||||
}
|
||||
|
||||
val verifier = taskFactory.apply(ContractVerifier::class.java)
|
||||
val verifier = taskFactory.apply(TransactionVerifier::class.java)
|
||||
|
||||
// Now execute the contract verifier task within the sandbox...
|
||||
verifier.apply(sandboxTx)
|
||||
@ -128,7 +133,7 @@ class DeterministicVerifier(
|
||||
val sandboxEx = SandboxException(
|
||||
Message.getMessageFromException(this),
|
||||
result.identifier,
|
||||
ClassSource.fromClassName(ContractVerifier::class.java.name),
|
||||
ClassSource.fromClassName(TransactionVerifier::class.java.name),
|
||||
ExecutionSummary(result.costs),
|
||||
this
|
||||
)
|
||||
|
@ -1,6 +1,9 @@
|
||||
package net.corda.node.internal.djvm
|
||||
|
||||
import net.corda.core.internal.SerializedStateAndRef
|
||||
import net.corda.core.serialization.AMQP_ENVELOPE_CACHE_INITIAL_CAPACITY
|
||||
import net.corda.core.serialization.AMQP_ENVELOPE_CACHE_PROPERTY
|
||||
import net.corda.core.serialization.DESERIALIZATION_CACHE_PROPERTY
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializationFactory
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
@ -22,14 +25,20 @@ class Serializer(
|
||||
init {
|
||||
val env = createSandboxSerializationEnv(classLoader, customSerializerNames, serializationWhitelists)
|
||||
factory = env.serializationFactory
|
||||
context = env.p2pContext
|
||||
context = env.p2pContext.withProperties(mapOf<Any, Any>(
|
||||
// Duplicate the P2P SerializationContext and give it
|
||||
// these extra properties, just for this transaction.
|
||||
AMQP_ENVELOPE_CACHE_PROPERTY to HashMap<Any, Any>(AMQP_ENVELOPE_CACHE_INITIAL_CAPACITY),
|
||||
DESERIALIZATION_CACHE_PROPERTY to HashMap<Any, Any>()
|
||||
))
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a list of [SerializedStateAndRef] objects into arrays
|
||||
* of deserialized sandbox objects. We will pass this array into
|
||||
* [net.corda.node.djvm.LtxFactory] to be transformed finally to
|
||||
* a list of [net.corda.core.contracts.StateAndRef] objects,
|
||||
* [LtxSupplierFactory][net.corda.node.djvm.LtxSupplierFactory]
|
||||
* to be transformed finally to a list of
|
||||
* [StateAndRef][net.corda.core.contracts.StateAndRef] objects,
|
||||
*/
|
||||
fun deserialize(stateRefs: List<SerializedStateAndRef>): Array<Array<out Any?>> {
|
||||
return stateRefs.map {
|
||||
|
@ -32,7 +32,6 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||
import net.corda.nodeapi.internal.persistence.contextTransaction
|
||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||
import org.apache.mina.util.ConcurrentHashSet
|
||||
import org.slf4j.Logger
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
@ -147,7 +146,7 @@ class NodeSchedulerService(private val clock: CordaClock,
|
||||
|
||||
// Used to de-duplicate flow starts in case a flow is starting but the corresponding entry hasn't been removed yet
|
||||
// from the database
|
||||
private val startingStateRefs = ConcurrentHashSet<ScheduledStateRef>()
|
||||
private val startingStateRefs: MutableSet<ScheduledStateRef> = ConcurrentHashMap.newKeySet<ScheduledStateRef>()
|
||||
private val mutex = ThreadBox(InnerState())
|
||||
private val schedulerTimerExecutor = Executors.newSingleThreadExecutor()
|
||||
|
||||
|
@ -16,6 +16,7 @@ import net.corda.core.internal.*
|
||||
import net.corda.core.internal.Version
|
||||
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_VERSION
|
||||
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
|
||||
import net.corda.core.internal.utilities.ZipBombDetector
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.core.node.services.vault.AttachmentQueryCriteria
|
||||
@ -33,6 +34,7 @@ import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||
import net.corda.nodeapi.internal.persistence.currentDBSession
|
||||
import net.corda.nodeapi.internal.withContractsInJar
|
||||
import org.hibernate.query.Query
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.FilterInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
@ -367,6 +369,9 @@ class NodeAttachmentService @JvmOverloads constructor(
|
||||
// set the hash field of the new attachment record.
|
||||
|
||||
val bytes = inputStream.readFully()
|
||||
require(!ZipBombDetector.scanZip(ByteArrayInputStream(bytes), servicesForResolution.networkParameters.maxTransactionSize.toLong())) {
|
||||
"The attachment is too large and exceeds both max transaction size and the maximum allowed compression ratio"
|
||||
}
|
||||
val id = bytes.sha256()
|
||||
if (!hasAttachment(id)) {
|
||||
checkIsAValidJAR(bytes.inputStream())
|
||||
|
@ -315,7 +315,7 @@ class CheckpointDumperImpl(private val checkpointStorage: CheckpointStorage, pri
|
||||
* the checkpoint agent source code
|
||||
*/
|
||||
private fun checkpointAgentRunning() = try {
|
||||
javaClass.classLoader.loadClass("net.corda.tools.CheckpointAgent").kotlin.companionObject
|
||||
Class.forName("net.corda.tools.CheckpointAgent", false, javaClass.classLoader).kotlin.companionObject
|
||||
} catch (e: ClassNotFoundException) {
|
||||
null
|
||||
}?.let { cls ->
|
||||
|
@ -63,7 +63,12 @@ class ErrorFlowTransition(
|
||||
status = Checkpoint.FlowStatus.FAILED,
|
||||
flowState = FlowState.Finished,
|
||||
checkpointState = startingState.checkpoint.checkpointState.copy(
|
||||
numberOfCommits = startingState.checkpoint.checkpointState.numberOfCommits + 1
|
||||
numberOfCommits = startingState.checkpoint.checkpointState.numberOfCommits + 1,
|
||||
invocationContext = if (startingState.checkpoint.checkpointState.invocationContext.arguments!!.isNotEmpty()) {
|
||||
startingState.checkpoint.checkpointState.invocationContext.copy(arguments = emptyList())
|
||||
} else {
|
||||
startingState.checkpoint.checkpointState.invocationContext
|
||||
}
|
||||
)
|
||||
)
|
||||
currentState = currentState.copy(
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.node.services.transactions
|
||||
|
||||
import net.corda.core.internal.BasicVerifier
|
||||
import net.corda.core.internal.Verifier
|
||||
import net.corda.core.serialization.ConstructorForDeserialization
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
@ -9,6 +8,7 @@ import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
|
||||
import net.corda.core.serialization.CordaSerializationTransformRename
|
||||
import net.corda.core.serialization.CordaSerializationTransformRenames
|
||||
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.djvm.SandboxConfiguration
|
||||
@ -80,13 +80,13 @@ class DeterministicVerifierFactoryService(
|
||||
override fun apply(ledgerTransaction: LedgerTransaction): LedgerTransaction {
|
||||
// Specialise the LedgerTransaction here so that
|
||||
// contracts are verified inside the DJVM!
|
||||
return ledgerTransaction.specialise(::specialise)
|
||||
return ledgerTransaction.specialise(::createDeterministicVerifier)
|
||||
}
|
||||
|
||||
private fun specialise(ltx: LedgerTransaction, classLoader: ClassLoader): Verifier {
|
||||
return (classLoader as? URLClassLoader)?.run {
|
||||
private fun createDeterministicVerifier(ltx: LedgerTransaction, serializationContext: SerializationContext): Verifier {
|
||||
return (serializationContext.deserializationClassLoader as? URLClassLoader)?.let { classLoader ->
|
||||
DeterministicVerifier(ltx, classLoader, createSandbox(classLoader.urLs))
|
||||
} ?: BasicVerifier(ltx, classLoader)
|
||||
} ?: throw IllegalStateException("Unsupported deserialization classloader type")
|
||||
}
|
||||
|
||||
private fun createSandbox(userSource: Array<URL>): SandboxConfiguration {
|
||||
|
@ -6,6 +6,7 @@ import net.corda.core.flows.FlowSession
|
||||
import net.corda.core.flows.NotarisationPayload
|
||||
import net.corda.core.flows.NotaryError
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.IdempotentFlow
|
||||
import net.corda.core.internal.PlatformVersionSwitches
|
||||
import net.corda.core.internal.notary.NotaryInternalException
|
||||
import net.corda.core.internal.notary.NotaryServiceFlow
|
||||
@ -25,7 +26,10 @@ import java.time.Duration
|
||||
* the caller, it is possible to raise a dispute and verify the validity of the transaction and subsequently
|
||||
* undo the commit of the input states (the exact mechanism still needs to be worked out).
|
||||
*/
|
||||
class NonValidatingNotaryFlow(otherSideSession: FlowSession, service: SinglePartyNotaryService, etaThreshold: Duration) : NotaryServiceFlow(otherSideSession, service, etaThreshold) {
|
||||
class NonValidatingNotaryFlow(otherSideSession: FlowSession, service: SinglePartyNotaryService, etaThreshold: Duration) :
|
||||
NotaryServiceFlow(otherSideSession, service, etaThreshold),
|
||||
IdempotentFlow
|
||||
{
|
||||
private val minPlatformVersion get() = serviceHub.networkParameters.minimumPlatformVersion
|
||||
|
||||
override fun extractParts(requestPayload: NotarisationPayload): TransactionParts {
|
||||
|
@ -271,7 +271,7 @@ class NodeVaultService(
|
||||
// This will cause a failure as we can't deserialize such states in the context of the `appClassloader`.
|
||||
// For now we ignore these states.
|
||||
// In the future we will use the AttachmentsClassloader to correctly deserialize and asses the relevancy.
|
||||
log.debug { "Could not deserialize state $idx from transaction $txId. Cause: $e" }
|
||||
log.warn("Could not deserialize state $idx from transaction $txId. Cause: $e")
|
||||
null
|
||||
}
|
||||
}.toMap()
|
||||
|
@ -123,7 +123,10 @@ class CordaServiceTest {
|
||||
val identityService = makeTestIdentityService(dummyNotary.identity)
|
||||
|
||||
Assertions.assertThatThrownBy { MockServices(cordappPackages, dummyNotary, identityService, dummyCashIssuer.keyPair, bankOfCorda.keyPair) }
|
||||
.isInstanceOf(ClassNotFoundException::class.java).hasMessage("Could not create jar file as the given package is not found on the classpath: com.r3.corda.sdk.tokens.money")
|
||||
.isInstanceOf(ClassNotFoundException::class.java)
|
||||
.hasMessageStartingWith("Could not create jar file as ")
|
||||
.hasMessageContaining("com.r3.corda.sdk.tokens.money")
|
||||
.hasMessageEndingWith(" not found on the classpath")
|
||||
}
|
||||
|
||||
@StartableByService
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.node.internal
|
||||
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.serialization.CustomSerializationScheme
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializationSchemeContext
|
||||
@ -16,7 +15,7 @@ class CustomSerializationSchemeScanningTest {
|
||||
|
||||
open class DummySerializationScheme : CustomSerializationScheme {
|
||||
override fun getSchemeId(): Int {
|
||||
return 7;
|
||||
return 7
|
||||
}
|
||||
|
||||
override fun <T : Any> deserialize(bytes: ByteSequence, clazz: Class<T>, context: SerializationSchemeContext): T {
|
||||
@ -34,9 +33,7 @@ class CustomSerializationSchemeScanningTest {
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `Can scan for custom serialization scheme and build a serialization scheme`() {
|
||||
val classLoader = Mockito.mock(ClassLoader::class.java)
|
||||
whenever(classLoader.loadClass(DummySerializationScheme::class.java.canonicalName)).thenAnswer { DummySerializationScheme::class.java }
|
||||
val scheme = scanForCustomSerializationScheme(DummySerializationScheme::class.java.canonicalName, classLoader)
|
||||
val scheme = scanForCustomSerializationScheme(DummySerializationScheme::class.java.name, this::class.java.classLoader)
|
||||
val mockContext = Mockito.mock(SerializationContext::class.java)
|
||||
assertFailsWith<DummySerializationSchemeException>("Tried to serialize with DummySerializationScheme") {
|
||||
scheme.serialize(Any::class.java, mockContext)
|
||||
@ -45,34 +42,28 @@ class CustomSerializationSchemeScanningTest {
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `verification fails with a helpful error if the class is not found in the classloader`() {
|
||||
val classLoader = Mockito.mock(ClassLoader::class.java)
|
||||
val missingClassName = DummySerializationScheme::class.java.canonicalName
|
||||
whenever(classLoader.loadClass(missingClassName)).thenAnswer { throw ClassNotFoundException()}
|
||||
val missingClassName = "org.testing.DoesNotExist"
|
||||
assertFailsWith<ConfigurationException>("$missingClassName was declared as a custom serialization scheme but could not " +
|
||||
"be found.") {
|
||||
scanForCustomSerializationScheme(missingClassName, classLoader)
|
||||
scanForCustomSerializationScheme(missingClassName, this::class.java.classLoader)
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `verification fails with a helpful error if the class is not a custom serialization scheme`() {
|
||||
val canonicalName = NonSerializationScheme::class.java.canonicalName
|
||||
val classLoader = Mockito.mock(ClassLoader::class.java)
|
||||
whenever(classLoader.loadClass(canonicalName)).thenAnswer { NonSerializationScheme::class.java }
|
||||
assertFailsWith<ConfigurationException>("$canonicalName was declared as a custom serialization scheme but does not " +
|
||||
val schemeName = NonSerializationScheme::class.java.name
|
||||
assertFailsWith<ConfigurationException>("$schemeName was declared as a custom serialization scheme but does not " +
|
||||
"implement CustomSerializationScheme.") {
|
||||
scanForCustomSerializationScheme(canonicalName, classLoader)
|
||||
scanForCustomSerializationScheme(schemeName, this::class.java.classLoader)
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `verification fails with a helpful error if the class does not have a no arg constructor`() {
|
||||
val classLoader = Mockito.mock(ClassLoader::class.java)
|
||||
val canonicalName = DummySerializationSchemeWithoutNoArgConstructor::class.java.canonicalName
|
||||
whenever(classLoader.loadClass(canonicalName)).thenAnswer { DummySerializationSchemeWithoutNoArgConstructor::class.java }
|
||||
assertFailsWith<ConfigurationException>("$canonicalName was declared as a custom serialization scheme but does not " +
|
||||
val schemeName = DummySerializationSchemeWithoutNoArgConstructor::class.java.name
|
||||
assertFailsWith<ConfigurationException>("$schemeName was declared as a custom serialization scheme but does not " +
|
||||
"have a no argument constructor.") {
|
||||
scanForCustomSerializationScheme(canonicalName, classLoader)
|
||||
scanForCustomSerializationScheme(schemeName, this::class.java.classLoader)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,15 +72,6 @@ dependencies {
|
||||
testCompile "com.github.detro:ghostdriver:$ghostdriver_version"
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://maven.scijava.org/content/repositories/public/'
|
||||
content {
|
||||
includeGroup 'com.github.detro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bootRepackage {
|
||||
enabled = false
|
||||
}
|
||||
|
@ -20,6 +20,9 @@ sourceSets {
|
||||
runtimeClasspath += main.output + test.output
|
||||
srcDir file('src/integration-test/kotlin')
|
||||
}
|
||||
resources {
|
||||
srcDir file('src/integration-test/resources')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,7 +53,12 @@ dependencies {
|
||||
// Corda integration dependencies
|
||||
cordaRuntime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
||||
|
||||
testCompile(project(':node-driver'))
|
||||
testCompile "org.slf4j:slf4j-simple:$slf4j_version"
|
||||
testCompile(project(':node-driver')) {
|
||||
// We already have a SLF4J implementation on our runtime classpath,
|
||||
// and we don't need another one.
|
||||
exclude group: 'org.apache.logging.log4j', module: 'log4j-slf4j-impl'
|
||||
}
|
||||
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
|
||||
testImplementation "junit:junit:$junit_version"
|
||||
|
@ -0,0 +1,4 @@
|
||||
org.slf4j.simpleLogger.defaultLogLevel=info
|
||||
org.slf4j.simpleLogger.showDateTime=true
|
||||
org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z
|
||||
org.slf4j.simpleLogger.logFile=System.out
|
@ -23,7 +23,10 @@ def javaHome = System.getProperty('java.home')
|
||||
def jarBaseName = "corda-${project.name}".toString()
|
||||
|
||||
configurations {
|
||||
deterministicLibraries.extendsFrom implementation
|
||||
deterministicLibraries {
|
||||
canBeConsumed = false
|
||||
extendsFrom implementation
|
||||
}
|
||||
deterministicArtifacts.extendsFrom deterministicLibraries
|
||||
}
|
||||
|
||||
@ -55,7 +58,7 @@ def originalJar = serializationJarTask.map { it.outputs.files.singleFile }
|
||||
|
||||
def patchSerialization = tasks.register('patchSerialization', Zip) {
|
||||
dependsOn serializationJarTask
|
||||
destinationDirectory = file("$buildDir/source-libs")
|
||||
destinationDirectory = layout.buildDirectory.dir('source-libs')
|
||||
metadataCharset 'UTF-8'
|
||||
archiveClassifier = 'transient'
|
||||
archiveExtension = 'jar'
|
||||
@ -157,7 +160,7 @@ def determinise = tasks.register('determinise', ProGuardTask) {
|
||||
def checkDeterminism = tasks.register('checkDeterminism', ProGuardTask)
|
||||
|
||||
def metafix = tasks.register('metafix', MetaFixerTask) {
|
||||
outputDir file("$buildDir/libs")
|
||||
outputDir = layout.buildDirectory.dir('libs')
|
||||
jars determinise
|
||||
suffix ""
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user