Deserialise LedgerTransaction into the sandbox for Contract.verify().

This commit is contained in:
Chris Rankin 2019-09-13 09:07:48 +01:00
parent 5666b2406a
commit 0b3b1a9884
21 changed files with 509 additions and 78 deletions

View File

@ -488,6 +488,7 @@ bintrayConfig {
'corda-finance-contracts', 'corda-finance-contracts',
'corda-node', 'corda-node',
'corda-node-api', 'corda-node-api',
'corda-node-djvm',
'corda-test-common', 'corda-test-common',
'corda-test-utils', 'corda-test-utils',
'corda-test-db', 'corda-test-db',

View File

@ -8,7 +8,6 @@ import net.corda.core.crypto.*
import net.corda.core.crypto.SecureHash.Companion.zeroHash import net.corda.core.crypto.SecureHash.Companion.zeroHash
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.accessLeafIndex
import net.corda.core.node.NotaryInfo import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
@ -307,37 +306,37 @@ class PartialMerkleTreeTest {
val pmt = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("1"), SecureHash.sha256("5"), SecureHash.sha256("0"), SecureHash.sha256("19"))) val pmt = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("1"), SecureHash.sha256("5"), SecureHash.sha256("0"), SecureHash.sha256("19")))
// First leaf. // First leaf.
assertEquals(0, pmt.accessLeafIndex(SecureHash.sha256("0"))) assertEquals(0, pmt.leafIndex(SecureHash.sha256("0")))
// Second leaf. // Second leaf.
assertEquals(1, pmt.accessLeafIndex(SecureHash.sha256("1"))) assertEquals(1, pmt.leafIndex(SecureHash.sha256("1")))
// A random leaf. // A random leaf.
assertEquals(5, pmt.accessLeafIndex(SecureHash.sha256("5"))) assertEquals(5, pmt.leafIndex(SecureHash.sha256("5")))
// The last leaf. // The last leaf.
assertEquals(19, pmt.accessLeafIndex(SecureHash.sha256("19"))) assertEquals(19, pmt.leafIndex(SecureHash.sha256("19")))
// The provided hash is not in the tree. // The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmt.accessLeafIndex(SecureHash.sha256("10")) } assertFailsWith<MerkleTreeException> { pmt.leafIndex(SecureHash.sha256("10")) }
// The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree). // The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree).
assertFailsWith<MerkleTreeException> { pmt.accessLeafIndex(SecureHash.sha256("30")) } assertFailsWith<MerkleTreeException> { pmt.leafIndex(SecureHash.sha256("30")) }
val pmtFirstElementOnly = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("0"))) val pmtFirstElementOnly = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("0")))
assertEquals(0, pmtFirstElementOnly.accessLeafIndex(SecureHash.sha256("0"))) assertEquals(0, pmtFirstElementOnly.leafIndex(SecureHash.sha256("0")))
// The provided hash is not in the tree. // The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmtFirstElementOnly.accessLeafIndex(SecureHash.sha256("10")) } assertFailsWith<MerkleTreeException> { pmtFirstElementOnly.leafIndex(SecureHash.sha256("10")) }
val pmtLastElementOnly = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("19"))) val pmtLastElementOnly = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("19")))
assertEquals(19, pmtLastElementOnly.accessLeafIndex(SecureHash.sha256("19"))) assertEquals(19, pmtLastElementOnly.leafIndex(SecureHash.sha256("19")))
// The provided hash is not in the tree. // The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmtLastElementOnly.accessLeafIndex(SecureHash.sha256("10")) } assertFailsWith<MerkleTreeException> { pmtLastElementOnly.leafIndex(SecureHash.sha256("10")) }
val pmtOneElement = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("5"))) val pmtOneElement = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("5")))
assertEquals(5, pmtOneElement.accessLeafIndex(SecureHash.sha256("5"))) assertEquals(5, pmtOneElement.leafIndex(SecureHash.sha256("5")))
// The provided hash is not in the tree. // The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmtOneElement.accessLeafIndex(SecureHash.sha256("10")) } assertFailsWith<MerkleTreeException> { pmtOneElement.leafIndex(SecureHash.sha256("10")) }
val pmtAllIncluded = PartialMerkleTree.build(merkleTree, sampleLeaves) val pmtAllIncluded = PartialMerkleTree.build(merkleTree, sampleLeaves)
for (i in 0..19) assertEquals(i, pmtAllIncluded.accessLeafIndex(SecureHash.sha256(i.toString()))) for (i in 0..19) assertEquals(i, pmtAllIncluded.leafIndex(SecureHash.sha256(i.toString())))
// The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree). // The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree).
assertFailsWith<MerkleTreeException> { pmtAllIncluded.accessLeafIndex(SecureHash.sha256("30")) } assertFailsWith<MerkleTreeException> { pmtAllIncluded.leafIndex(SecureHash.sha256("30")) }
} }
@Test @Test

View File

@ -1,6 +1,7 @@
package net.corda.core.crypto package net.corda.core.crypto
import net.corda.core.CordaException import net.corda.core.CordaException
import net.corda.core.CordaInternal
import net.corda.core.KeepForDJVM import net.corda.core.KeepForDJVM
import net.corda.core.crypto.SecureHash.Companion.zeroHash import net.corda.core.crypto.SecureHash.Companion.zeroHash
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
@ -169,8 +170,9 @@ class PartialMerkleTree(val root: PartialTree) {
* @return leaf-index of this component (starting from zero). * @return leaf-index of this component (starting from zero).
* @throws MerkleTreeException if the provided hash is not in the tree. * @throws MerkleTreeException if the provided hash is not in the tree.
*/ */
@CordaInternal
@Throws(MerkleTreeException::class) @Throws(MerkleTreeException::class)
internal fun leafIndex(leaf: SecureHash): Int { fun leafIndex(leaf: SecureHash): Int {
// Special handling if the tree consists of one node only. // Special handling if the tree consists of one node only.
if (root is PartialTree.IncludedLeaf && root.hash == leaf) return 0 if (root is PartialTree.IncludedLeaf && root.hash == leaf) return 0
val flagPath = mutableListOf<Boolean>() val flagPath = mutableListOf<Boolean>()

View File

@ -1,6 +1,8 @@
@file:KeepForDJVM
package net.corda.core.node.services package net.corda.core.node.services
import net.corda.core.DoNotImplement import net.corda.core.DoNotImplement
import net.corda.core.KeepForDJVM
import net.corda.core.contracts.Attachment import net.corda.core.contracts.Attachment
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION

View File

@ -78,6 +78,7 @@ private constructor(
checkNotaryWhitelisted() checkNotaryWhitelisted()
} }
@KeepForDJVM
companion object { companion object {
private val logger = contextLogger() private val logger = contextLogger()
@ -126,6 +127,41 @@ private constructor(
verifierFactory = ::BasicVerifier verifierFactory = ::BasicVerifier
) )
} }
/**
* This factory function will create an instance of [LedgerTransaction]
* that will be used inside the DJVM sandbox.
*/
@CordaInternal
fun createForSandbox(
inputs: List<StateAndRef<ContractState>>,
outputs: List<TransactionState<ContractState>>,
commands: List<CommandWithParties<CommandData>>,
attachments: List<Attachment>,
id: SecureHash,
notary: Party?,
timeWindow: TimeWindow?,
privacySalt: PrivacySalt,
networkParameters: NetworkParameters,
references: List<StateAndRef<ContractState>>): LedgerTransaction {
return LedgerTransaction(
inputs = inputs,
outputs = outputs,
commands = commands,
attachments = attachments,
id = id,
notary = notary,
timeWindow = timeWindow,
privacySalt = privacySalt,
networkParameters = networkParameters,
references = references,
componentGroups = null,
serializedInputs = null,
serializedReferences = null,
isAttachmentTrusted = { true },
verifierFactory = ::BasicVerifier
)
}
} }
val inputStates: List<ContractState> get() = inputs.map { it.state.data } val inputStates: List<ContractState> get() = inputs.map { it.state.data }
@ -179,8 +215,8 @@ private constructor(
* Pass all of this [LedgerTransaction] object's serialized state to a [transformer] function. * Pass all of this [LedgerTransaction] object's serialized state to a [transformer] function.
*/ */
@CordaInternal @CordaInternal
fun <T> transform(transformer: (List<ComponentGroup>?, List<SerializedStateAndRef>?, List<SerializedStateAndRef>?) -> T): T { fun <T> transform(transformer: (List<ComponentGroup>, List<SerializedStateAndRef>, List<SerializedStateAndRef>) -> T): T {
return transformer(componentGroups, serializedInputs, serializedReferences) return transformer(componentGroups ?: emptyList(), serializedInputs ?: emptyList(), serializedReferences ?: emptyList())
} }
/** /**

View File

@ -1,7 +1,6 @@
package net.corda.core.internal package net.corda.core.internal
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.PartialMerkleTree
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
@ -37,5 +36,3 @@ fun createLedgerTransaction(
fun createContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable) = TransactionVerificationException.ContractCreationError(txId, contractClass, cause) 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) fun createContractRejection(txId: SecureHash, contract: Contract, cause: Throwable) = TransactionVerificationException.ContractRejection(txId, contract, cause)
fun PartialMerkleTree.accessLeafIndex(id: SecureHash) = this.leafIndex(id)

View File

@ -181,12 +181,18 @@ dependencies {
// Sandbox for deterministic contract verification // Sandbox for deterministic contract verification
compile "net.corda:corda-djvm:$djvm_version" compile "net.corda:corda-djvm:$djvm_version"
compile "net.corda:corda-djvm-serialization:$djvm_version" compile "net.corda:corda-djvm-serialization:$djvm_version"
compile(project(':node:djvm')) {
transitive = false
}
jdkRt "net.corda:deterministic-rt:latest.integration" jdkRt "net.corda:deterministic-rt:latest.integration"
deterministic project(path: ':core-deterministic', configuration: 'deterministicArtifacts') deterministic project(path: ':core-deterministic', configuration: 'deterministicArtifacts')
deterministic project(path: ':serialization-deterministic', configuration: 'deterministicArtifacts') deterministic project(path: ':serialization-deterministic', configuration: 'deterministicArtifacts')
deterministic("net.corda:corda-djvm-deserializers:$djvm_version") { deterministic("net.corda:corda-djvm-deserializers:$djvm_version") {
transitive = false transitive = false
} }
deterministic(project(':node:djvm')) {
transitive = false
}
deterministic "org.slf4j:slf4j-nop:$slf4j_version" deterministic "org.slf4j:slf4j-nop:$slf4j_version"
testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}" testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"

20
node/djvm/build.gradle Normal file
View File

@ -0,0 +1,20 @@
apply from: '../../deterministic.gradle'
apply plugin: 'com.jfrog.artifactory'
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'java-library'
description 'Internal Corda Node modules for deterministic contract verification.'
dependencies {
api 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
api project(path: ':core-deterministic', configuration: 'deterministicArtifacts')
api project(path: ':serialization-deterministic', configuration: 'deterministicArtifacts')
}
jar {
archiveBaseName = 'corda-node-djvm'
}
publish {
name jar.archiveBaseName.get()
}

View File

@ -0,0 +1,55 @@
package net.corda.node.djvm
import net.corda.core.contracts.Attachment
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import java.io.InputStream
import java.security.PublicKey
import java.util.Collections.unmodifiableList
import java.util.function.Function
class AttachmentBuilder : Function<Array<Any>?, List<Attachment>?> {
private val attachments = mutableListOf<Attachment>()
private fun <T> unmodifiable(list: List<T>): List<T> {
return if(list.isEmpty()) {
emptyList()
} else {
unmodifiableList(list)
}
}
override fun apply(inputs: Array<Any>?): List<Attachment>? {
return if (inputs == null) {
unmodifiable(attachments)
} else {
@Suppress("unchecked_cast")
attachments.add(SandboxAttachment(
signerKeys = inputs[0] as List<PublicKey>,
size = inputs[1] as Int,
id = inputs[2] as SecureHash,
attachment = inputs[3],
streamer = inputs[4] as Function<in Any, out InputStream>
))
null
}
}
}
/**
* This represents an [Attachment] from within the sandbox.
*/
class SandboxAttachment(
override val signerKeys: List<PublicKey>,
override val size: Int,
override val id: SecureHash,
private val attachment: Any,
private val streamer: Function<Any, out InputStream>
) : Attachment {
@Suppress("OverridingDeprecatedMember")
override val signers: List<Party> = emptyList()
override fun open(): InputStream {
return streamer.apply(attachment)
}
}

View File

@ -0,0 +1,37 @@
package net.corda.node.djvm
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.CommandWithParties
import net.corda.core.internal.lazyMapped
import java.security.PublicKey
import java.util.function.Function
class CommandBuilder : Function<Array<Any?>, List<CommandWithParties<CommandData>>> {
@Suppress("unchecked_cast")
override fun apply(inputs: Array<Any?>): List<CommandWithParties<CommandData>> {
val signers = inputs[0] as? List<List<PublicKey>> ?: emptyList()
val commandsData = inputs[1] as? List<CommandData> ?: emptyList()
val partialMerkleLeafIndices = inputs[2] as? IntArray
return if (partialMerkleLeafIndices != null) {
check(commandsData.size <= signers.size) {
"Invalid Transaction. Fewer Signers (${signers.size}) than CommandData (${commandsData.size}) objects"
}
if (partialMerkleLeafIndices.isNotEmpty()) {
check(partialMerkleLeafIndices.max()!! < signers.size) {
"Invalid Transaction. A command with no corresponding signer detected"
}
}
commandsData.lazyMapped { commandData, index ->
CommandWithParties(signers[partialMerkleLeafIndices[index]], emptyList(), commandData)
}
} else {
check(commandsData.size == signers.size) {
"Invalid Transaction. Sizes of CommandData (${commandsData.size}) and Signers (${signers.size}) do not match"
}
commandsData.lazyMapped { commandData, index ->
CommandWithParties(signers[index], emptyList(), commandData)
}
}
}
}

View File

@ -0,0 +1,24 @@
package net.corda.node.djvm
import net.corda.core.contracts.ComponentGroupEnum
import net.corda.core.internal.TransactionDeserialisationException
import net.corda.core.internal.lazyMapped
import net.corda.core.utilities.OpaqueBytes
import java.util.function.Function
class ComponentBuilder : Function<Array<Any?>, List<*>> {
@Suppress("unchecked_cast")
override fun apply(inputs: Array<Any?>): List<*> {
val deserializer = inputs[0] as Function<in Any?, out Any?>
val groupType = inputs[1] as ComponentGroupEnum
val components = (inputs[2] as Array<ByteArray>).map { OpaqueBytes(it) }
return components.lazyMapped { component, index ->
try {
deserializer.apply(component.bytes)
} catch (e: Exception) {
throw TransactionDeserialisationException(groupType, index, e)
}
}
}
}

View File

@ -0,0 +1,43 @@
@file:JvmName("LtxConstants")
package net.corda.node.djvm
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.node.NetworkParameters
import net.corda.core.transactions.LedgerTransaction
import java.util.function.Function
private const val TX_INPUTS = 0
private const val TX_OUTPUTS = 1
private const val TX_COMMANDS = 2
private const val TX_ATTACHMENTS = 3
private const val TX_ID = 4
private const val TX_NOTARY = 5
private const val TX_TIME_WINDOW = 6
private const val TX_PRIVACY_SALT = 7
private const val TX_NETWORK_PARAMETERS = 8
private const val TX_REFERENCES = 9
class LtxFactory : Function<Array<Any?>, LedgerTransaction> {
@Suppress("unchecked_cast")
override fun apply(txArgs: Array<Any?>): LedgerTransaction {
return LedgerTransaction.createForSandbox(
inputs = (txArgs[TX_INPUTS] as Array<Array<Any?>>).map { it.toStateAndRef() },
outputs = (txArgs[TX_OUTPUTS] as? List<TransactionState<ContractState>>) ?: emptyList(),
commands = (txArgs[TX_COMMANDS] as? List<CommandWithParties<CommandData>>) ?: emptyList(),
attachments = (txArgs[TX_ATTACHMENTS] as? List<Attachment>) ?: emptyList(),
id = txArgs[TX_ID] as SecureHash,
notary = txArgs[TX_NOTARY] as? Party,
timeWindow = txArgs[TX_TIME_WINDOW] as? TimeWindow,
privacySalt = txArgs[TX_PRIVACY_SALT] as PrivacySalt,
networkParameters = txArgs[TX_NETWORK_PARAMETERS] as NetworkParameters,
references = (txArgs[TX_REFERENCES] as Array<Array<Any?>>).map { it.toStateAndRef() }
)
}
private fun Array<*>.toStateAndRef(): StateAndRef<ContractState> {
return StateAndRef(this[0] as TransactionState<*>, this[1] as StateRef)
}
}

View File

@ -33,14 +33,11 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.days import net.corda.core.utilities.days
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.minutes import net.corda.core.utilities.minutes
import net.corda.djvm.analysis.AnalysisConfiguration
import net.corda.djvm.analysis.Whitelist
import net.corda.djvm.source.* import net.corda.djvm.source.*
import net.corda.node.CordaClock import net.corda.node.CordaClock
import net.corda.node.VersionInfo import net.corda.node.VersionInfo
import net.corda.node.internal.classloading.requireAnnotation import net.corda.node.internal.classloading.requireAnnotation
import net.corda.node.internal.cordapp.* import net.corda.node.internal.cordapp.*
import net.corda.node.internal.djvm.DeterministicVerifier
import net.corda.node.internal.rpc.proxies.AuthenticatedRpcOpsProxy import net.corda.node.internal.rpc.proxies.AuthenticatedRpcOpsProxy
import net.corda.node.internal.rpc.proxies.ThreadContextAdjustingRpcOpsProxy import net.corda.node.internal.rpc.proxies.ThreadContextAdjustingRpcOpsProxy
import net.corda.node.services.ContractUpgradeHandler import net.corda.node.services.ContractUpgradeHandler
@ -68,6 +65,7 @@ import net.corda.node.services.persistence.*
import net.corda.node.services.rpc.CheckpointDumper import net.corda.node.services.rpc.CheckpointDumper
import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.statemachine.* import net.corda.node.services.statemachine.*
import net.corda.node.services.transactions.DeterministicVerifierFactoryService
import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.services.transactions.SimpleNotaryService import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.node.services.upgrade.ContractUpgradeServiceImpl import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
@ -98,8 +96,6 @@ import rx.Observable
import rx.Scheduler import rx.Scheduler
import java.io.IOException import java.io.IOException
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
import java.net.URL
import java.net.URLClassLoader
import java.nio.file.Path import java.nio.file.Path
import java.security.KeyPair import java.security.KeyPair
import java.security.KeyStoreException import java.security.KeyStoreException
@ -221,6 +217,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
).closeOnStop() ).closeOnStop()
@Suppress("LeakingThis") @Suppress("LeakingThis")
val transactionVerifierService = InMemoryTransactionVerifierService(transactionVerifierWorkerCount).tokenize() val transactionVerifierService = InMemoryTransactionVerifierService(transactionVerifierWorkerCount).tokenize()
val deterministicVerifierFactoryService = DeterministicVerifierFactoryService(djvmBootstrapSource, djvmCordaSource).tokenize()
val contractUpgradeService = ContractUpgradeServiceImpl(cacheFactory).tokenize() val contractUpgradeService = ContractUpgradeServiceImpl(cacheFactory).tokenize()
val auditService = DummyAuditService().tokenize() val auditService = DummyAuditService().tokenize()
@Suppress("LeakingThis") @Suppress("LeakingThis")
@ -1081,28 +1078,11 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val ledgerTransaction = servicesForResolution.specialise(ltx) val ledgerTransaction = servicesForResolution.specialise(ltx)
// Do nothing unless we have Corda's deterministic libraries. // Do nothing unless we have Corda's deterministic libraries.
val cordaSource = djvmCordaSource ?: return ledgerTransaction djvmCordaSource ?: return ledgerTransaction
// Specialise the LedgerTransaction here so that // Specialise the LedgerTransaction here so that
// contracts are verified inside the DJVM! // contracts are verified inside the DJVM!
return ledgerTransaction.specialise { tx, cl -> return ledgerTransaction.specialise(deterministicVerifierFactoryService::specialise)
(cl as? URLClassLoader)?.run { DeterministicVerifier(tx, cl, createSandbox(cordaSource, cl.urLs)) } ?: BasicVerifier(tx, cl)
}
}
private fun createSandbox(cordaSource: UserSource, userSource: Array<URL>): AnalysisConfiguration {
return AnalysisConfiguration.createRoot(
userSource = cordaSource,
whitelist = Whitelist.MINIMAL,
bootstrapSource = djvmBootstrapSource
).createChild(
userSource = UserPathSource(userSource),
newMinimumSeverityLevel = null,
visibleAnnotations = setOf(
CordaSerializable::class.java,
ConstructorForDeserialization::class.java
)
)
} }
} }
} }

View File

@ -0,0 +1,32 @@
package net.corda.node.internal.djvm
import net.corda.core.contracts.Attachment
import net.corda.core.serialization.serialize
import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.node.djvm.AttachmentBuilder
import java.util.function.Function
class AttachmentFactory(
private val classLoader: SandboxClassLoader,
private val taskFactory: Function<in Any, out Function<in Any?, out Any?>>,
private val sandboxBasicInput: Function<in Any?, out Any?>,
private val serializer: Serializer
) {
private val sandboxOpenAttachment: Function<in Attachment, out Any?> = classLoader.createForImport(
Function(Attachment::open).andThen(sandboxBasicInput)
)
fun toSandbox(attachments: List<Attachment>): Any? {
val builder = classLoader.createTaskFor(taskFactory, 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
))
}
return builder.apply(null)
}
}

View File

@ -0,0 +1,19 @@
package net.corda.node.internal.djvm
import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.node.djvm.CommandBuilder
import java.util.function.Function
class CommandFactory(
private val classLoader: SandboxClassLoader,
private val taskFactory: Function<in Any, out Function<in Any?, out Any?>>
) {
fun toSandbox(signers: Any?, commands: Any?, partialMerkleLeafIndices: IntArray?): Any? {
val builder = classLoader.createTaskFor(taskFactory, CommandBuilder::class.java)
return builder.apply(arrayOf(
signers,
commands,
partialMerkleLeafIndices
))
}
}

View File

@ -0,0 +1,43 @@
@file:JvmName("ComponentUtils")
package net.corda.node.internal.djvm
import net.corda.core.contracts.ComponentGroupEnum
import net.corda.core.crypto.componentHash
import net.corda.core.transactions.ComponentGroup
import net.corda.core.transactions.FilteredComponentGroup
import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.node.djvm.ComponentBuilder
import java.util.function.Function
class ComponentFactory(
private val classLoader: SandboxClassLoader,
private val taskFactory: Function<in Any, out Function<in Any?, out Any?>>,
private val sandboxBasicInput: Function<in Any?, out Any?>,
private val serializer: Serializer,
private val componentGroups: List<ComponentGroup>
) {
fun toSandbox(
groupType: ComponentGroupEnum,
clazz: Class<*>
): Any? {
val components = (componentGroups.firstOrNull(groupType::isSameType) ?: return null).components
val componentBytes = Array(components.size) { idx -> components[idx].bytes }
return classLoader.createTaskFor(taskFactory, ComponentBuilder::class.java).apply(arrayOf(
classLoader.createForImport(serializer.deserializerFor(clazz)),
sandboxBasicInput.apply(groupType),
componentBytes
))
}
fun calculateLeafIndicesFor(groupType: ComponentGroupEnum): IntArray? {
val componentGroup = componentGroups.firstOrNull(groupType::isSameType) as? FilteredComponentGroup ?: return null
val componentHashes = componentGroup.components.mapIndexed { index, component ->
componentHash(componentGroup.nonces[index], component)
}
return componentHashes.map { componentGroup.partialMerkleTree.leafIndex(it) }.toIntArray()
}
}
private fun ComponentGroupEnum.isSameType(group: ComponentGroup): Boolean {
return group.groupIndex == ordinal
}

View File

@ -1,15 +1,19 @@
package net.corda.node.internal.djvm package net.corda.node.internal.djvm
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ComponentGroupEnum.*
import net.corda.core.contracts.TransactionState
import net.corda.core.contracts.TransactionVerificationException import net.corda.core.contracts.TransactionVerificationException
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.internal.ContractVerifier import net.corda.core.internal.*
import net.corda.core.internal.Verifier import net.corda.core.serialization.serialize
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.djvm.SandboxConfiguration import net.corda.djvm.SandboxConfiguration
import net.corda.djvm.analysis.AnalysisConfiguration import net.corda.djvm.analysis.AnalysisConfiguration
import net.corda.djvm.execution.* import net.corda.djvm.execution.*
import net.corda.djvm.messages.Message import net.corda.djvm.messages.Message
import net.corda.djvm.source.ClassSource import net.corda.djvm.source.ClassSource
import net.corda.node.djvm.LtxFactory
class DeterministicVerifier( class DeterministicVerifier(
ltx: LedgerTransaction, ltx: LedgerTransaction,
@ -23,24 +27,67 @@ class DeterministicVerifier(
profile = ExecutionProfile.DEFAULT, profile = ExecutionProfile.DEFAULT,
enableTracing = false enableTracing = false
) )
val verifierClass = ClassSource.fromClassName(ContractVerifier::class.java.name)
val result = IsolatedTask(verifierClass.qualifiedClassName, configuration).run {
val executor = Executor(classLoader)
val result = IsolatedTask(ltx.id.toString(), configuration).run {
val taskFactory = classLoader.createRawTaskFactory()
val sandboxBasicInput = classLoader.createBasicInput()
/**
* Deserialise the [LedgerTransaction] again into something
* that we can execute inside the DJVM's sandbox.
*/
val sandboxTx = ltx.transform { componentGroups, serializedInputs, serializedReferences -> val sandboxTx = ltx.transform { componentGroups, serializedInputs, serializedReferences ->
val serializer = Serializer(classLoader)
val componentFactory = ComponentFactory(
classLoader,
taskFactory,
sandboxBasicInput,
serializer,
componentGroups
)
val attachmentFactory = AttachmentFactory(
classLoader,
taskFactory,
sandboxBasicInput,
serializer
)
val idData = ltx.id.serialize()
val notaryData = ltx.notary?.serialize()
val timeWindowData = ltx.timeWindow?.serialize()
val privacySaltData = ltx.privacySalt.serialize()
val networkingParametersData = ltx.networkParameters?.serialize()
val createSandboxTx = classLoader.createTaskFor(taskFactory, LtxFactory::class.java)
createSandboxTx.apply(arrayOf(
serializer.deserialize(serializedInputs),
componentFactory.toSandbox(OUTPUTS_GROUP, TransactionState::class.java),
CommandFactory(classLoader, taskFactory).toSandbox(
componentFactory.toSandbox(SIGNERS_GROUP, List::class.java),
componentFactory.toSandbox(COMMANDS_GROUP, CommandData::class.java),
componentFactory.calculateLeafIndicesFor(COMMANDS_GROUP)
),
attachmentFactory.toSandbox(ltx.attachments),
serializer.deserialize(idData),
serializer.deserialize(notaryData),
serializer.deserialize(timeWindowData),
serializer.deserialize(privacySaltData),
serializer.deserialize(networkingParametersData),
serializer.deserialize(serializedReferences)
))
} }
val verifier = classLoader.loadClassForSandbox(verifierClass).newInstance() val verifier = classLoader.createTaskFor(taskFactory, ContractVerifier::class.java)
// Now execute the contract verifier task within the sandbox... // Now execute the contract verifier task within the sandbox...
executor.execute(verifier, sandboxTx) verifier.apply(sandboxTx)
} }
result.exception?.run { result.exception?.run {
val sandboxEx = SandboxException( val sandboxEx = SandboxException(
Message.getMessageFromException(this), Message.getMessageFromException(this),
result.identifier, result.identifier,
verifierClass, ClassSource.fromClassName(ContractVerifier::class.java.name),
ExecutionSummary(result.costs), ExecutionSummary(result.costs),
this this
) )
@ -51,7 +98,7 @@ class DeterministicVerifier(
@Throws(Exception::class) @Throws(Exception::class)
override fun close() { override fun close() {
analysisConfiguration.closeAll() // analysisConfiguration.closeAll()
} }
} }

View File

@ -1,24 +0,0 @@
package net.corda.node.internal.djvm
import java.lang.reflect.Constructor
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method
class Executor(classLoader: ClassLoader) {
private val constructor: Constructor<out Any>
private val executeMethod: Method
init {
val taskClass = classLoader.loadClass("sandbox.RawTask")
constructor = taskClass.getDeclaredConstructor(classLoader.loadClass("sandbox.java.util.function.Function"))
executeMethod = taskClass.getMethod("apply", Any::class.java)
}
fun execute(task: Any, input: Any?): Any? {
return try {
executeMethod.invoke(constructor.newInstance(task), input)
} catch (ex: InvocationTargetException) {
throw ex.targetException
}
}
}

View File

@ -0,0 +1,59 @@
package net.corda.node.internal.djvm
import net.corda.core.internal.SerializedStateAndRef
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationFactory
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.serialize
import net.corda.core.utilities.ByteSequence
import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.djvm.serialization.createSandboxSerializationEnv
import net.corda.djvm.serialization.deserializeTo
import net.corda.node.djvm.ComponentBuilder
import java.util.function.Function
class Serializer(private val classLoader: SandboxClassLoader) {
private val factory: SerializationFactory
private val context: SerializationContext
init {
val env = createSandboxSerializationEnv(classLoader)
factory = env.serializationFactory
context = env.p2pContext
}
/**
* Convert a list of [SerializedStateAndRef] objects into arrays
* of deserialized sandbox objects. We will pass this array into
* [net.corda.node.djvm.LtxFactory] to be transformed finally to
* a list of [net.corda.core.contracts.StateAndRef] objects,
*/
fun deserialize(stateRefs: List<SerializedStateAndRef>): Array<Array<Any?>> {
return stateRefs.map {
arrayOf(deserialize(it.serializedState), deserialize(it.ref.serialize()))
}.toTypedArray()
}
/**
* Generate a [Function] that deserializes a [ByteArray] into an instance
* of the given sandbox class. We import this [Function] into the sandbox
* so that [ComponentBuilder] can deserialize objects lazily.
*/
fun deserializerFor(clazz: Class<*>): Function<ByteArray?, out Any?> {
val sandboxClass = classLoader.toSandboxClass(clazz)
return Function { bytes ->
bytes?.run {
ByteSequence.of(this).deserializeTo(sandboxClass, classLoader, factory, context)
}
}
}
fun deserializeTo(clazz: Class<*>, bytes: ByteSequence): Any {
val sandboxClass = classLoader.toSandboxClass(clazz)
return bytes.deserializeTo(sandboxClass, classLoader, factory, context)
}
inline fun <reified T : Any> deserialize(bytes: SerializedBytes<T>?): Any? {
return deserializeTo(T::class.java, bytes ?: return null)
}
}

View File

@ -0,0 +1,52 @@
package net.corda.node.services.transactions
import net.corda.core.internal.BasicVerifier
import net.corda.core.internal.Verifier
import net.corda.core.serialization.ConstructorForDeserialization
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.LedgerTransaction
import net.corda.djvm.analysis.AnalysisConfiguration
import net.corda.djvm.analysis.Whitelist
import net.corda.djvm.source.ApiSource
import net.corda.djvm.source.UserPathSource
import net.corda.djvm.source.UserSource
import net.corda.node.internal.djvm.DeterministicVerifier
import java.net.URL
import java.net.URLClassLoader
class DeterministicVerifierFactoryService(
private val bootstrapSource: ApiSource,
private val cordaSource: UserSource?
) : SingletonSerializeAsToken(), AutoCloseable {
fun specialise(ltx: LedgerTransaction, classLoader: ClassLoader): Verifier {
return (classLoader as? URLClassLoader)?.run {
DeterministicVerifier(ltx, classLoader, createSandbox(classLoader.urLs))
} ?: BasicVerifier(ltx, classLoader)
}
private fun createSandbox(userSource: Array<URL>): AnalysisConfiguration {
return AnalysisConfiguration.createRoot(
userSource = cordaSource!!,
whitelist = Whitelist.MINIMAL,
visibleAnnotations = setOf(
CordaSerializable::class.java,
ConstructorForDeserialization::class.java,
DeprecatedConstructorForDeserialization::class.java
),
bootstrapSource = bootstrapSource
).createChild(
userSource = UserPathSource(userSource),
newMinimumSeverityLevel = null,
visibleAnnotations = emptySet()
)
}
override fun close() {
bootstrapSource.use {
cordaSource?.close()
}
}
}

View File

@ -20,6 +20,7 @@ include 'docs'
include 'node-api' include 'node-api'
include 'node' include 'node'
include 'node:capsule' include 'node:capsule'
include 'node:djvm'
include 'client:jackson' include 'client:jackson'
include 'client:jfx' include 'client:jfx'
include 'client:mock' include 'client:mock'