Merge branch 'release/os/4.8' into ronanb/ENT-6699/update-java-version

This commit is contained in:
Ronan Browne 2022-03-16 12:52:00 +00:00
commit 89da70a205
118 changed files with 2742 additions and 621 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,9 @@ plugins {
}
configurations {
testData
testData {
canBeResolved = false
}
}
dependencies {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,5 +8,17 @@ the context of a node. However, as everything else depends on the core module, w
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`

View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,3 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import static org.gradle.api.JavaVersion.VERSION_1_8
plugins {
id 'org.jetbrains.kotlin.jvm'
id 'net.corda.plugins.publish-utils'
@ -17,8 +14,12 @@ apply from: "${rootProject.projectDir}/java8.gradle"
description 'Serialization support for the DJVM'
configurations {
sandboxTesting
jdkRt
sandboxTesting {
canBeConsumed = false
}
jdkRt {
canBeConsumed = false
}
}
dependencies {
@ -56,6 +57,11 @@ jar {
}
}
tasks.withType(Javadoc).configureEach {
// We have no public or protected Java classes to document.
enabled = false
}
tasks.withType(Test).configureEach {
useJUnitPlatform()
systemProperty 'deterministic-rt.path', configurations.jdkRt.asPath
@ -66,7 +72,7 @@ tasks.withType(Test).configureEach {
}
publish {
name jar.archiveBaseName.get()
name jar.archiveBaseName
}
idea {

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