diff --git a/core-tests/src/test/kotlin/net/corda/coretests/transactions/TransactionTests.kt b/core-tests/src/test/kotlin/net/corda/coretests/transactions/TransactionTests.kt index 47d4171c1d..62254d6b4e 100644 --- a/core-tests/src/test/kotlin/net/corda/coretests/transactions/TransactionTests.kt +++ b/core-tests/src/test/kotlin/net/corda/coretests/transactions/TransactionTests.kt @@ -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>() - 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 = emptyList() override val signerKeys: List = 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) diff --git a/core/src/main/kotlin/net/corda/core/internal/ConstraintsUtils.kt b/core/src/main/kotlin/net/corda/core/internal/ConstraintsUtils.kt index c05ae94680..2cdb80fad1 100644 --- a/core/src/main/kotlin/net/corda/core/internal/ConstraintsUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/ConstraintsUtils.kt @@ -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() @@ -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 + } } /** diff --git a/core/src/main/kotlin/net/corda/core/internal/TransactionVerifierServiceInternal.kt b/core/src/main/kotlin/net/corda/core/internal/TransactionVerifierServiceInternal.kt index 2a8c13036e..58d647af6f 100644 --- a/core/src/main/kotlin/net/corda/core/internal/TransactionVerifierServiceInternal.kt +++ b/core/src/main/kotlin/net/corda/core/internal/TransactionVerifierServiceInternal.kt @@ -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) = 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 + + protected companion object { + @JvmField + val logger = loggerFor() + } + + /** + * 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> = ltx.inputs.map(StateAndRef::state) private val allStates: List> = inputStates + ltx.references.map(StateAndRef::state) + ltx.outputs - companion object { - val logger = contextLogger() + private companion object { + private val logger = loggerFor() } /** @@ -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 { 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>) { // [Set] of "from" (encumbered states). val encumberedSet = mutableSetOf() @@ -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) { // 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, Unit> { +class TransactionVerifier(private val transactionClassLoader: ClassLoader) : Function, 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) { 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) diff --git a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt index c7b00f60bd..25dfa7f293 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt @@ -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 + 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() {} } diff --git a/core/src/test/kotlin/net/corda/core/internal/internalAccessTestHelpers.kt b/core/src/test/kotlin/net/corda/core/internal/internalAccessTestHelpers.kt index 1a6f880f1d..16a6e6bef8 100644 --- a/core/src/test/kotlin/net/corda/core/internal/internalAccessTestHelpers.kt +++ b/core/src/test/kotlin/net/corda/core/internal/internalAccessTestHelpers.kt @@ -5,10 +5,12 @@ import net.corda.core.crypto.DigestService import net.corda.core.crypto.SecureHash import net.corda.core.identity.Party import net.corda.core.node.NetworkParameters +import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.internal.AttachmentsClassLoaderCache import net.corda.core.transactions.ComponentGroup import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.WireTransaction +import java.util.function.Supplier /** * A set of functions in core:test that allows testing of core internal classes in the core-tests project. @@ -38,7 +40,17 @@ fun createLedgerTransaction( isAttachmentTrusted: (Attachment) -> Boolean, attachmentsClassLoaderCache: AttachmentsClassLoaderCache, digestService: DigestService = DigestService.default -): LedgerTransaction = LedgerTransaction.create(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references, componentGroups, serializedInputs, serializedReferences, isAttachmentTrusted, attachmentsClassLoaderCache, digestService) +): LedgerTransaction = LedgerTransaction.create( + inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references, componentGroups, serializedInputs, serializedReferences, isAttachmentTrusted, attachmentsClassLoaderCache, digestService +).specialise(::PassthroughVerifier) fun createContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable) = TransactionVerificationException.ContractCreationError(txId, contractClass, cause) fun createContractRejection(txId: SecureHash, contract: Contract, cause: Throwable) = TransactionVerificationException.ContractRejection(txId, contract, cause) + +/** + * Verify the [LedgerTransaction] we already have. + */ +private class PassthroughVerifier(ltx: LedgerTransaction, context: SerializationContext) : AbstractVerifier(ltx, context.deserializationClassLoader) { + override val transaction: Supplier + get() = Supplier { ltx } +} diff --git a/node/djvm/src/main/kotlin/net/corda/node/djvm/AttachmentBuilder.kt b/node/djvm/src/main/kotlin/net/corda/node/djvm/AttachmentBuilder.kt index f3a205ba38..561b5bcd76 100644 --- a/node/djvm/src/main/kotlin/net/corda/node/djvm/AttachmentBuilder.kt +++ b/node/djvm/src/main/kotlin/net/corda/node/djvm/AttachmentBuilder.kt @@ -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?, List?> { private val attachments = mutableListOf() @@ -28,17 +35,30 @@ class AttachmentBuilder : Function?, List?> { } override fun apply(inputs: Array?): List? { + @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, size = inputs[SIZE_IDX] as Int, id = inputs[ID_IDX] as SecureHash, attachment = inputs[ATTACHMENT_IDX], streamer = inputs[STREAMER_IDX] as Function - )) + ) + + if (inputs.size > VERSION_IDX) { + attachment = ContractAttachment.create( + attachment = attachment, + contract = inputs[CONTRACT_IDX] as String, + additionalContracts = (inputs[ADDITIONAL_CONTRACT_IDX] as Array).toSet(), + uploader = inputs[UPLOADER_IDX] as? String, + signerKeys = inputs[CONTRACT_SIGNER_KEYS_IDX] as List, + version = inputs[VERSION_IDX] as Int + ) + } + + attachments.add(attachment) null } } @@ -47,7 +67,7 @@ class AttachmentBuilder : Function?, List?> { /** * This represents an [Attachment] from within the sandbox. */ -class SandboxAttachment( +private class SandboxAttachment( override val signerKeys: List, override val size: Int, override val id: SecureHash, diff --git a/node/src/integration-test/kotlin/net/corda/contracts/mutator/MutatorContract.kt b/node/src/integration-test/kotlin/net/corda/contracts/mutator/MutatorContract.kt index cffcf18b3d..239525c576 100644 --- a/node/src/integration-test/kotlin/net/corda/contracts/mutator/MutatorContract.kt +++ b/node/src/integration-test/kotlin/net/corda/contracts/mutator/MutatorContract.kt @@ -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 { diff --git a/node/src/integration-test/kotlin/net/corda/node/CashIssueAndPaymentTest.kt b/node/src/integration-test/kotlin/net/corda/node/CashIssueAndPaymentTest.kt index 399e1d9c2c..2da38e1509 100644 --- a/node/src/integration-test/kotlin/net/corda/node/CashIssueAndPaymentTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/CashIssueAndPaymentTest.kt @@ -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"), diff --git a/node/src/integration-test/kotlin/net/corda/node/ContractCannotMutateTransactionTest.kt b/node/src/integration-test/kotlin/net/corda/node/ContractCannotMutateTransactionTest.kt index eecfe203ec..62a92dc14f 100644 --- a/node/src/integration-test/kotlin/net/corda/node/ContractCannotMutateTransactionTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/ContractCannotMutateTransactionTest.kt @@ -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) diff --git a/node/src/integration-test/kotlin/net/corda/node/ContractWithCordappFixupTest.kt b/node/src/integration-test/kotlin/net/corda/node/ContractWithCordappFixupTest.kt index 50e5b1b1bd..77f267aed7 100644 --- a/node/src/integration-test/kotlin/net/corda/node/ContractWithCordappFixupTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/ContractWithCordappFixupTest.kt @@ -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): DriverParameters { + fun driverParameters(cordapps: List, 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()) ) diff --git a/node/src/integration-test/kotlin/net/corda/node/ContractWithCustomSerializerTest.kt b/node/src/integration-test/kotlin/net/corda/node/ContractWithCustomSerializerTest.kt index ffb2d297b1..442214e13e 100644 --- a/node/src/integration-test/kotlin/net/corda/node/ContractWithCustomSerializerTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/ContractWithCustomSerializerTest.kt @@ -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() diff --git a/node/src/integration-test/kotlin/net/corda/node/ContractWithGenericTypeTest.kt b/node/src/integration-test/kotlin/net/corda/node/ContractWithGenericTypeTest.kt index d23c137dda..4dfb1f17e6 100644 --- a/node/src/integration-test/kotlin/net/corda/node/ContractWithGenericTypeTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/ContractWithGenericTypeTest.kt @@ -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() diff --git a/node/src/integration-test/kotlin/net/corda/node/ContractWithMissingCustomSerializerTest.kt b/node/src/integration-test/kotlin/net/corda/node/ContractWithMissingCustomSerializerTest.kt index 2110ff3cfe..78ee896844 100644 --- a/node/src/integration-test/kotlin/net/corda/node/ContractWithMissingCustomSerializerTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/ContractWithMissingCustomSerializerTest.kt @@ -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 ) } diff --git a/node/src/integration-test/kotlin/net/corda/node/ContractWithSerializationWhitelistTest.kt b/node/src/integration-test/kotlin/net/corda/node/ContractWithSerializationWhitelistTest.kt index 2a9ae80195..9c6d809d77 100644 --- a/node/src/integration-test/kotlin/net/corda/node/ContractWithSerializationWhitelistTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/ContractWithSerializationWhitelistTest.kt @@ -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) ) } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DeterministicCashIssueAndPaymentTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicCashIssueAndPaymentTest.kt index 4997cac5e3..9b3bdf77ef 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/DeterministicCashIssueAndPaymentTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicCashIssueAndPaymentTest.kt @@ -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"), diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractCannotMutateTransactionTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractCannotMutateTransactionTest.kt index 68b8c3531c..41c80ea7d9 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractCannotMutateTransactionTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractCannotMutateTransactionTest.kt @@ -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) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractCryptoTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractCryptoTest.kt index 275693f4d7..5d28ae41b8 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractCryptoTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractCryptoTest.kt @@ -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( diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractWithCustomSerializerTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractWithCustomSerializerTest.kt index 3630fbcf3c..447cd2d6a6 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractWithCustomSerializerTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractWithCustomSerializerTest.kt @@ -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, 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 { diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractWithGenericTypeTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractWithGenericTypeTest.kt index c3c440eaf6..d2cae60136 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractWithGenericTypeTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractWithGenericTypeTest.kt @@ -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() diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractWithSerializationWhitelistTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractWithSerializationWhitelistTest.kt index 97ecbf014a..9b0e057453 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractWithSerializationWhitelistTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractWithSerializationWhitelistTest.kt @@ -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, 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 { diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DeterministicEvilContractCannotModifyStatesTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicEvilContractCannotModifyStatesTest.kt index f2d455dce4..5188f0b4fd 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/DeterministicEvilContractCannotModifyStatesTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicEvilContractCannotModifyStatesTest.kt @@ -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 { CordaRPCClient(hostAndPort = alice.rpcAddress) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/NonDeterministicContractVerifyTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/NonDeterministicContractVerifyTest.kt index 264502e448..a200d5db41 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/NonDeterministicContractVerifyTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/NonDeterministicContractVerifyTest.kt @@ -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( diff --git a/node/src/integration-test/kotlin/net/corda/node/services/SandboxAttachmentsTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/SandboxAttachmentsTest.kt index c825357581..e868566f58 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/SandboxAttachmentsTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/SandboxAttachmentsTest.kt @@ -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( diff --git a/node/src/main/kotlin/net/corda/node/internal/djvm/AttachmentFactory.kt b/node/src/main/kotlin/net/corda/node/internal/djvm/AttachmentFactory.kt index 9a616aa813..d272a7428e 100644 --- a/node/src/main/kotlin/net/corda/node/internal/djvm/AttachmentFactory.kt +++ b/node/src/main/kotlin/net/corda/node/internal/djvm/AttachmentFactory.kt @@ -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): 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 { + 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) + } + } } diff --git a/node/src/main/kotlin/net/corda/node/internal/djvm/DeterministicVerifier.kt b/node/src/main/kotlin/net/corda/node/internal/djvm/DeterministicVerifier.kt index f1880fbf54..3263868aa8 100644 --- a/node/src/main/kotlin/net/corda/node/internal/djvm/DeterministicVerifier.kt +++ b/node/src/main/kotlin/net/corda/node/internal/djvm/DeterministicVerifier.kt @@ -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(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 )