Validate LedgerTransaction deserialised from AttachmentsClassLoader. (#7049) (#7052)

This commit is contained in:
Chris Rankin 2022-01-31 09:40:01 +00:00 committed by GitHub
parent 7752fc8c9d
commit 758a69f904
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 212 additions and 104 deletions

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

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

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

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

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