Merge branch 'release/os/4.9' of https://github.com/corda/corda into release/os/4.9

This commit is contained in:
Ronan Browne 2022-02-14 16:59:10 +00:00
commit 2902b737fc
41 changed files with 270 additions and 134 deletions

View File

@ -319,7 +319,7 @@ pipeline {
'./gradlew',
COMMON_GRADLE_PARAMS,
'docker:pushDockerImage',
'-Pdocker.image.repository=corda/community-node',
'-Pdocker.image.repository=corda/corda',
'--image OFFICIAL'
].join(' ')
}

View File

@ -3,9 +3,9 @@
# PR Checklist:
- [ ] Have you run the unit, integration and smoke tests as described [here](https://docs.corda.net/head/testing.html)?
- [ ] If you added public APIs, did you write the JavaDocs?
- [ ] If the changes are of interest to application developers, have you added them to the [changelog](https://docs.corda.net/head/changelog.html) (`/docs/source/changelog.rst`), and potentially the [release notes](https://docs.corda.net/head/release-notes.html) (`/docs/source/release-notes.rst`)?
- [ ] If you are contributing for the first time, please read the [contributor agreement](https://docs.corda.net/head/contributing.html) now and add a comment to this pull request stating that your PR is in accordance with the [Developer's Certificate of Origin](https://docs.corda.net/head/contributing.html#merging-the-changes-back-into-corda).
- [ ] Have you run the unit, integration and smoke tests as described [here](https://docs.r3.com/en/platform/corda/4.8/open-source/testing.html)?
- [ ] If you added public APIs, did you write the JavaDocs/kdocs?
- [ ] If the changes are of interest to application developers, have you added them to the changelog, and potentially the [release notes](https://docs.corda.net/head/release-notes.html) (`https://docs.r3.com/en/platform/corda/4.8/open-source/release-notes.html`)?
- [ ] If you are contributing for the first time, please read the [contributor agreement](https://docs.r3.com/en/platform/corda/4.8/open-source/contributing.html) now and add a comment to this pull request stating that your PR is in accordance with the [Developer's Certificate of Origin](https://docs.r3.com/en/platform/corda/4.8/open-source/contributing.html#merging-the-changes-back-into-corda).
Thanks for your code, it's appreciated! :)

View File

@ -2,4 +2,4 @@
Corda is an open-source project and contributions are welcome!
To find out how to contribute, please see our [contributing docs](https://docs.corda.net/head/contributing-index.html).
To find out how to contribute, please see our [contributing docs](https://docs.r3.com/en/platform/corda/4.8/open-source/contributing.html).

View File

@ -39,7 +39,7 @@ Corda is an open source blockchain project, designed for business from the start
Corda is an open-source project and contributions are welcome!
To find out how to contribute, please see our [contributing docs](https://docs.corda.net/head/contributing-index.html).
To find out how to contribute, please see our [contributing docs](https://docs.r3.com/en/platform/corda/4.8/open-source/contributing.html).
## License

View File

@ -11,10 +11,10 @@ java8MinUpdateVersion=171
# When incrementing platformVersion make sure to update #
# net.corda.core.internal.CordaUtilsKt.PLATFORM_VERSION as well. #
# ***************************************************************#
platformVersion=10
platformVersion=11
guavaVersion=28.0-jre
# Quasar version to use with Java 8:
quasarVersion=0.7.13_r3
quasarVersion=0.7.14_r3
# Quasar version to use with Java 11:
quasarVersion11=0.8.1_r3
jdkClassifier11=jdk11

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

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

@ -28,7 +28,9 @@ import java.util.jar.JarInputStream
// *Internal* Corda-specific utilities.
const val PLATFORM_VERSION = 10
// When incrementing platformVersion make sure to update PLATFORM_VERSION in constants.properties as well.
const val PLATFORM_VERSION = 11
fun ServicesForResolution.ensureMinimumPlatformVersion(requiredMinPlatformVersion: Int, feature: String) {
checkMinimumPlatformVersion(networkParameters.minimumPlatformVersion, requiredMinPlatformVersion, feature)

View File

@ -13,6 +13,7 @@ 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
@ -33,7 +34,7 @@ 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
@ -47,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) {
@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>()
}
/**
@ -66,7 +105,7 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla
*
* @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()
@ -93,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() {
@ -106,6 +144,7 @@ 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.mapTo(LinkedHashSet(), TransactionState<*>::contract)
@ -210,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>()
@ -306,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.
@ -392,19 +433,15 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla
throw ContractConstraintRejection(ltx.id, contract)
}
}
/**
* Placeholder function for the contract verification logic.
*/
abstract fun verifyContracts()
}
/**
* 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<Supplier<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())
@ -440,16 +477,33 @@ class ContractVerifier(private val transactionClassLoader: ClassLoader) : Functi
}
}
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 ContractRejection(ltx.id, contract, e)

View File

@ -18,7 +18,7 @@ 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.ContractVerifier
import net.corda.core.internal.AbstractVerifier
import net.corda.core.internal.SerializedStateAndRef
import net.corda.core.internal.Verifier
import net.corda.core.internal.castIfPossible
@ -824,7 +824,7 @@ private constructor(
private class BasicVerifier(
ltx: LedgerTransaction,
private val serializationContext: SerializationContext
) : Verifier(ltx, serializationContext.deserializationClassLoader) {
) : AbstractVerifier(ltx, serializationContext.deserializationClassLoader) {
init {
// This is a sanity check: We should only instantiate this
@ -836,9 +836,15 @@ private class BasicVerifier(
// 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) {
@ -870,21 +876,6 @@ private class BasicVerifier(
}
}
}
/**
* Check the transaction 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.
*/
override fun verifyContracts() {
try {
ContractVerifier(transactionClassLoader).apply(Supplier(::createTransaction))
} catch (e: TransactionVerificationException) {
logger.error("Error validating transaction ${ltx.id}.", e.cause)
throw e
}
}
}
/**
@ -892,10 +883,10 @@ private class BasicVerifier(
*
* THIS CLASS IS NOT PUBLIC API, AND IS DELIBERATELY PRIVATE!
*/
@Suppress("unused_parameter")
@CordaInternal
private class NoOpVerifier(ltx: LedgerTransaction, serializationContext: SerializationContext)
: Verifier(ltx, serializationContext.deserializationClassLoader) {
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 verifyContracts() {}
override fun verify() {}
}

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

@ -1,4 +1,4 @@
FROM azul/zulu-openjdk:8u192
FROM azul/zulu-openjdk:8u312
## Add packages, clean cache, create dirs, create corda user and change ownership
RUN apt-get update && \
@ -63,4 +63,4 @@ COPY --chown=corda:corda starting-node.conf /opt/corda/starting-node.conf
USER "corda"
EXPOSE ${MY_P2P_PORT} ${MY_RPC_PORT} ${MY_RPC_ADMIN_PORT}
WORKDIR /opt/corda
CMD ["run-corda"]
CMD ["run-corda"]

View File

@ -1,4 +1,4 @@
FROM azul/zulu-openjdk:8u192
FROM azul/zulu-openjdk:8u312
## Add packages, clean cache, create dirs, create corda user and change ownership
RUN apt-get update && \

View File

@ -112,12 +112,11 @@ class DatabaseTransaction(
} finally {
clearException()
contextTransactionOrNull = outerTransaction
}
if (outerTransaction == null) {
synchronized(this) {
closed = true
boundary.onNext(CordaPersistence.Boundary(id, committed))
if (outerTransaction == null) {
synchronized(this) {
closed = true
boundary.onNext(CordaPersistence.Boundary(id, committed))
}
}
}
}

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

@ -95,9 +95,11 @@ class MutatorContract : Contract {
}
}
private class ExtraSpecialise(ltx: LedgerTransaction, ctx: SerializationContext)
: Verifier(ltx, ctx.deserializationClassLoader) {
override fun verifyContracts() {}
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 {

View File

@ -30,12 +30,12 @@ class CashIssueAndPaymentTest {
private val configOverrides = mapOf(NodeConfiguration::reloadCheckpointAfterSuspend.name to true)
private val CASH_AMOUNT = 500.DOLLARS
fun parametersFor(): DriverParameters {
fun parametersFor(runInProcess: Boolean = false): DriverParameters {
return DriverParameters(
systemProperties = mapOf("co.paralleluniverse.fibers.verifyInstrumentation" to "false"),
portAllocation = incrementalPortAllocation(),
startNodesInProcess = false,
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, startInProcess = false, validating = true)),
startNodesInProcess = runInProcess,
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, startInProcess = runInProcess, validating = true)),
notaryCustomOverrides = configOverrides,
cordappsForAllNodes = listOf(
findCordapp("net.corda.finance.contracts"),

View File

@ -23,7 +23,7 @@ class ContractCannotMutateTransactionTest {
private val mutatorFlowCorDapp = cordappWithPackages("net.corda.flows.mutator").signed()
private val mutatorContractCorDapp = cordappWithPackages("net.corda.contracts.mutator").signed()
fun driverParameters(runInProcess: Boolean): DriverParameters {
fun driverParameters(runInProcess: Boolean = false): DriverParameters {
return DriverParameters(
portAllocation = incrementalPortAllocation(),
startNodesInProcess = runInProcess,
@ -35,7 +35,7 @@ class ContractCannotMutateTransactionTest {
@Test(timeout = 300_000)
fun testContractCannotModifyTransaction() {
driver(driverParameters(runInProcess = false)) {
driver(driverParameters()) {
val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
val txID = CordaRPCClient(hostAndPort = alice.rpcAddress)
.start(user.username, user.password)

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

@ -13,6 +13,7 @@ import kotlin.test.assertTrue
class NodeRPCTests {
private val CORDA_VERSION_REGEX = "\\d+(\\.\\d+)?(\\.\\d+)?(-\\w+)?".toRegex()
private val CORDA_VENDOR = "Corda Open Source"
private val CORDA_VENDOR_CE = "Corda Community Edition"
private val CORDAPPS = listOf(FINANCE_CONTRACTS_CORDAPP, FINANCE_WORKFLOWS_CORDAPP)
private val CORDAPP_TYPES = setOf("Contract CorDapp", "Workflow CorDapp")
private val CLASSIFIER = if (SystemUtils.IS_JAVA_11) "-jdk11" else ""
@ -29,7 +30,7 @@ class NodeRPCTests {
val nodeDiagnosticInfo = startNode().get().rpc.nodeDiagnosticInfo()
assertTrue(nodeDiagnosticInfo.version.matches(CORDA_VERSION_REGEX))
assertEquals(PLATFORM_VERSION, nodeDiagnosticInfo.platformVersion)
assertEquals(CORDA_VENDOR, nodeDiagnosticInfo.vendor)
assertTrue(nodeDiagnosticInfo.vendor == CORDA_VENDOR || nodeDiagnosticInfo.vendor == CORDA_VENDOR_CE)
nodeDiagnosticInfo.cordapps.forEach { println("${it.shortName} ${it.type}") }
assertEquals(CORDAPPS.size, nodeDiagnosticInfo.cordapps.size)
assertEquals(CORDAPP_TYPES, nodeDiagnosticInfo.cordapps.map { it.type }.toSet())

View File

@ -32,11 +32,11 @@ class DeterministicCashIssueAndPaymentTest {
@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)),
notaryCustomOverrides = configOverrides,
cordappsForAllNodes = listOf(
findCordapp("net.corda.finance.contracts"),

View File

@ -28,7 +28,7 @@ class DeterministicContractCannotMutateTransactionTest {
@JvmField
val djvmSources = DeterministicSourcesRule()
fun driverParameters(runInProcess: Boolean): DriverParameters {
fun driverParameters(runInProcess: Boolean = false): DriverParameters {
return DriverParameters(
portAllocation = incrementalPortAllocation(),
startNodesInProcess = runInProcess,
@ -42,7 +42,7 @@ class DeterministicContractCannotMutateTransactionTest {
@Test(timeout = 300_000)
fun testContractCannotModifyTransaction() {
driver(driverParameters(runInProcess = false)) {
driver(driverParameters()) {
val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
val txID = CordaRPCClient(hostAndPort = alice.rpcAddress)
.start(user.username, user.password)

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

@ -34,7 +34,7 @@ class DeterministicEvilContractCannotModifyStatesTest {
@JvmField
val djvmSources = DeterministicSourcesRule()
fun driverParameters(runInProcess: Boolean): DriverParameters {
fun driverParameters(runInProcess: Boolean = false): DriverParameters {
return DriverParameters(
portAllocation = incrementalPortAllocation(),
startNodesInProcess = runInProcess,
@ -53,7 +53,7 @@ class DeterministicEvilContractCannotModifyStatesTest {
@Test(timeout = 300_000)
fun testContractThatTriesToModifyStates() {
val evilData = MutableDataObject(5000)
driver(driverParameters(runInProcess = false)) {
driver(driverParameters()) {
val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
val ex = assertFailsWith<DeterministicVerificationException> {
CordaRPCClient(hostAndPort = alice.rpcAddress)

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

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

View File

@ -687,10 +687,14 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
open fun startShell() {
if (configuration.shouldInitCrashShell()) {
val isShellStarted = InteractiveShell.startShellIfInstalled(configuration, cordappLoader)
configuration.sshd?.port?.let {
log.info("Binding Shell SSHD server on port $it.")
if (isShellStarted) {
log.info("Binding Shell SSHD server on port $it.")
} else {
log.info("SSH port defined but corda-shell is not installed in node's drivers directory")
}
}
InteractiveShell.startShellIfInstalled(configuration, cordappLoader)
}
}

View File

@ -263,11 +263,19 @@ open class NodeStartup : NodeStartupLogging {
Node.printBasicNodeInfo("Node for \"$name\" started up and registered in $elapsed sec")
// Don't start the shell if there's no console attached.
if (node.configuration.shouldStartLocalShell()) {
val isShellStarted = if (node.configuration.shouldStartLocalShell()) {
InteractiveShell.runLocalShellIfInstalled(node::stop)
} else {
false
}
if (node.configuration.shouldStartSSHDaemon()) {
Node.printBasicNodeInfo("SSH server listening on port", node.configuration.sshd!!.port.toString())
if (isShellStarted) {
Node.printBasicNodeInfo("SSH server listening on port", node.configuration.sshd!!.port.toString())
} else {
Node.printBasicNodeInfo(
"SSH server not started. SSH port is defined but the corda-shell is not installed in node's drivers directory"
)
}
}
},
{ th ->

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
@ -26,10 +27,14 @@ 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 ->
@ -113,7 +118,7 @@ class DeterministicVerifier(
))
}
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

@ -17,28 +17,36 @@ object InteractiveShell {
private const val RUN_LOCAL_SHELL_METHOD = "runLocalShell"
private const val SET_USER_INFO_METHOD = "setUserInfo"
fun startShellIfInstalled(configuration: NodeConfiguration, cordappLoader: CordappLoader) {
if (isShellInstalled()) {
fun startShellIfInstalled(configuration: NodeConfiguration, cordappLoader: CordappLoader): Boolean {
return if (isShellInstalled()) {
try {
val shellConfiguration = configuration.toShellConfigMap()
setUnsafeUsers(configuration)
startShell(shellConfiguration, cordappLoader)
true
} catch (e: Exception) {
log.error("Shell failed to start", e)
false
}
} else {
false
}
}
/**
* Only call this after [startShellIfInstalled] has been called or the required classes will not be loaded into the current classloader.
*/
fun runLocalShellIfInstalled(onExit: () -> Unit = {}) {
if (isShellInstalled()) {
fun runLocalShellIfInstalled(onExit: () -> Unit = {}): Boolean {
return if (isShellInstalled()) {
try {
runLocalShell(onExit)
true
} catch (e: Exception) {
log.error("Shell failed to start", e)
false
}
} else {
false
}
}

View File

@ -77,6 +77,7 @@ def webTask = tasks.getByPath(':testing:testserver:testcapsule::assemble')
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask, webTask]) {
ext.rpcUsers = [['username': "demo", 'password': "demo", 'permissions': ["StartFlow.net.corda.attachmentdemo.AttachmentDemoFlow",
"InvokeRpc.partiesFromName",
"InvokeRpc.notaryPartyFromX500Name",
"InvokeRpc.attachmentExists",
"InvokeRpc.openAttachment",
"InvokeRpc.uploadAttachment",

View File

@ -5,10 +5,13 @@ import net.corda.core.utilities.getOrThrow
import net.corda.node.services.Permissions.Companion.all
import net.corda.testing.core.DUMMY_BANK_A_NAME
import net.corda.testing.core.DUMMY_BANK_B_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.DummyClusterSpec
import net.corda.testing.node.internal.findCordapp
import org.junit.Test
import java.util.concurrent.CompletableFuture.supplyAsync
@ -21,7 +24,8 @@ class AttachmentDemoTest {
driver(DriverParameters(
portAllocation = incrementalPortAllocation(),
startNodesInProcess = true,
cordappsForAllNodes = listOf(findCordapp("net.corda.attachmentdemo.contracts"), findCordapp("net.corda.attachmentdemo.workflows")))
cordappsForAllNodes = listOf(findCordapp("net.corda.attachmentdemo.contracts"), findCordapp("net.corda.attachmentdemo.workflows")),
notarySpecs = listOf(NotarySpec(name = DUMMY_NOTARY_NAME, cluster = DummyClusterSpec(clusterSize = 1))))
) {
val demoUser = listOf(User("demo", "demo", setOf(all())))
val (nodeA, nodeB) = listOf(

View File

@ -5,6 +5,7 @@ import net.corda.attachmentdemo.contracts.AttachmentContract
import net.corda.attachmentdemo.workflows.AttachmentDemoFlow
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.Emoji
import net.corda.core.internal.InputStreamAndHash
import net.corda.core.messaging.CordaRPCOps
@ -65,7 +66,7 @@ fun sender(rpc: CordaRPCOps, numOfClearBytes: Int = 1024) { // default size 1K.
private fun sender(rpc: CordaRPCOps, inputStream: InputStream, hash: SecureHash.SHA256) {
// Get the identity key of the other side (the recipient).
val notaryParty = rpc.partiesFromName("Notary", false).firstOrNull() ?: throw IllegalArgumentException("Couldn't find notary party")
val notaryParty = rpc.notaryPartyFromX500Name(CordaX500Name.parse("O=Notary Service,L=Zurich,C=CH")) ?: throw IllegalArgumentException("Couldn't find notary party")
val bankBParty = rpc.partiesFromName("Bank B", false).firstOrNull() ?: throw IllegalArgumentException("Couldn't find Bank B party")
// Make sure we have the file in storage
if (!rpc.attachmentExists(hash)) {