mirror of
https://github.com/corda/corda.git
synced 2024-12-24 07:06:44 +00:00
Deserialise LedgerTransaction into the sandbox for Contract.verify().
This commit is contained in:
parent
5666b2406a
commit
0b3b1a9884
@ -488,6 +488,7 @@ bintrayConfig {
|
||||
'corda-finance-contracts',
|
||||
'corda-node',
|
||||
'corda-node-api',
|
||||
'corda-node-djvm',
|
||||
'corda-test-common',
|
||||
'corda-test-utils',
|
||||
'corda-test-db',
|
||||
|
@ -8,7 +8,6 @@ import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.SecureHash.Companion.zeroHash
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.accessLeafIndex
|
||||
import net.corda.core.node.NotaryInfo
|
||||
import net.corda.core.node.services.IdentityService
|
||||
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")))
|
||||
// First leaf.
|
||||
assertEquals(0, pmt.accessLeafIndex(SecureHash.sha256("0")))
|
||||
assertEquals(0, pmt.leafIndex(SecureHash.sha256("0")))
|
||||
// Second leaf.
|
||||
assertEquals(1, pmt.accessLeafIndex(SecureHash.sha256("1")))
|
||||
assertEquals(1, pmt.leafIndex(SecureHash.sha256("1")))
|
||||
// A random leaf.
|
||||
assertEquals(5, pmt.accessLeafIndex(SecureHash.sha256("5")))
|
||||
assertEquals(5, pmt.leafIndex(SecureHash.sha256("5")))
|
||||
// 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.
|
||||
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).
|
||||
assertFailsWith<MerkleTreeException> { pmt.accessLeafIndex(SecureHash.sha256("30")) }
|
||||
assertFailsWith<MerkleTreeException> { pmt.leafIndex(SecureHash.sha256("30")) }
|
||||
|
||||
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.
|
||||
assertFailsWith<MerkleTreeException> { pmtFirstElementOnly.accessLeafIndex(SecureHash.sha256("10")) }
|
||||
assertFailsWith<MerkleTreeException> { pmtFirstElementOnly.leafIndex(SecureHash.sha256("10")) }
|
||||
|
||||
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.
|
||||
assertFailsWith<MerkleTreeException> { pmtLastElementOnly.accessLeafIndex(SecureHash.sha256("10")) }
|
||||
assertFailsWith<MerkleTreeException> { pmtLastElementOnly.leafIndex(SecureHash.sha256("10")) }
|
||||
|
||||
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.
|
||||
assertFailsWith<MerkleTreeException> { pmtOneElement.accessLeafIndex(SecureHash.sha256("10")) }
|
||||
assertFailsWith<MerkleTreeException> { pmtOneElement.leafIndex(SecureHash.sha256("10")) }
|
||||
|
||||
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).
|
||||
assertFailsWith<MerkleTreeException> { pmtAllIncluded.accessLeafIndex(SecureHash.sha256("30")) }
|
||||
assertFailsWith<MerkleTreeException> { pmtAllIncluded.leafIndex(SecureHash.sha256("30")) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.CordaException
|
||||
import net.corda.core.CordaInternal
|
||||
import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.crypto.SecureHash.Companion.zeroHash
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
@ -169,8 +170,9 @@ class PartialMerkleTree(val root: PartialTree) {
|
||||
* @return leaf-index of this component (starting from zero).
|
||||
* @throws MerkleTreeException if the provided hash is not in the tree.
|
||||
*/
|
||||
@CordaInternal
|
||||
@Throws(MerkleTreeException::class)
|
||||
internal fun leafIndex(leaf: SecureHash): Int {
|
||||
fun leafIndex(leaf: SecureHash): Int {
|
||||
// Special handling if the tree consists of one node only.
|
||||
if (root is PartialTree.IncludedLeaf && root.hash == leaf) return 0
|
||||
val flagPath = mutableListOf<Boolean>()
|
||||
|
@ -1,6 +1,8 @@
|
||||
@file:KeepForDJVM
|
||||
package net.corda.core.node.services
|
||||
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
|
||||
|
@ -78,6 +78,7 @@ private constructor(
|
||||
checkNotaryWhitelisted()
|
||||
}
|
||||
|
||||
@KeepForDJVM
|
||||
companion object {
|
||||
private val logger = contextLogger()
|
||||
|
||||
@ -126,6 +127,41 @@ private constructor(
|
||||
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 }
|
||||
@ -179,8 +215,8 @@ private constructor(
|
||||
* Pass all of this [LedgerTransaction] object's serialized state to a [transformer] function.
|
||||
*/
|
||||
@CordaInternal
|
||||
fun <T> transform(transformer: (List<ComponentGroup>?, List<SerializedStateAndRef>?, List<SerializedStateAndRef>?) -> T): T {
|
||||
return transformer(componentGroups, serializedInputs, serializedReferences)
|
||||
fun <T> transform(transformer: (List<ComponentGroup>, List<SerializedStateAndRef>, List<SerializedStateAndRef>) -> T): T {
|
||||
return transformer(componentGroups ?: emptyList(), serializedInputs ?: emptyList(), serializedReferences ?: emptyList())
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,7 +1,6 @@
|
||||
package net.corda.core.internal
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.PartialMerkleTree
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.Party
|
||||
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 createContractRejection(txId: SecureHash, contract: Contract, cause: Throwable) = TransactionVerificationException.ContractRejection(txId, contract, cause)
|
||||
|
||||
fun PartialMerkleTree.accessLeafIndex(id: SecureHash) = this.leafIndex(id)
|
@ -181,12 +181,18 @@ dependencies {
|
||||
// Sandbox for deterministic contract verification
|
||||
compile "net.corda:corda-djvm:$djvm_version"
|
||||
compile "net.corda:corda-djvm-serialization:$djvm_version"
|
||||
compile(project(':node:djvm')) {
|
||||
transitive = false
|
||||
}
|
||||
jdkRt "net.corda:deterministic-rt:latest.integration"
|
||||
deterministic project(path: ':core-deterministic', configuration: 'deterministicArtifacts')
|
||||
deterministic project(path: ':serialization-deterministic', configuration: 'deterministicArtifacts')
|
||||
deterministic("net.corda:corda-djvm-deserializers:$djvm_version") {
|
||||
transitive = false
|
||||
}
|
||||
deterministic(project(':node:djvm')) {
|
||||
transitive = false
|
||||
}
|
||||
deterministic "org.slf4j:slf4j-nop:$slf4j_version"
|
||||
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
|
||||
|
20
node/djvm/build.gradle
Normal file
20
node/djvm/build.gradle
Normal 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()
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
43
node/djvm/src/main/kotlin/net/corda/node/djvm/LtxFactory.kt
Normal file
43
node/djvm/src/main/kotlin/net/corda/node/djvm/LtxFactory.kt
Normal 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)
|
||||
}
|
||||
}
|
@ -33,14 +33,11 @@ import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
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.node.CordaClock
|
||||
import net.corda.node.VersionInfo
|
||||
import net.corda.node.internal.classloading.requireAnnotation
|
||||
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.ThreadContextAdjustingRpcOpsProxy
|
||||
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.schema.NodeSchemaService
|
||||
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.SimpleNotaryService
|
||||
import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
|
||||
@ -98,8 +96,6 @@ import rx.Observable
|
||||
import rx.Scheduler
|
||||
import java.io.IOException
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.net.URL
|
||||
import java.net.URLClassLoader
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyPair
|
||||
import java.security.KeyStoreException
|
||||
@ -221,6 +217,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
).closeOnStop()
|
||||
@Suppress("LeakingThis")
|
||||
val transactionVerifierService = InMemoryTransactionVerifierService(transactionVerifierWorkerCount).tokenize()
|
||||
val deterministicVerifierFactoryService = DeterministicVerifierFactoryService(djvmBootstrapSource, djvmCordaSource).tokenize()
|
||||
val contractUpgradeService = ContractUpgradeServiceImpl(cacheFactory).tokenize()
|
||||
val auditService = DummyAuditService().tokenize()
|
||||
@Suppress("LeakingThis")
|
||||
@ -1081,28 +1078,11 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
val ledgerTransaction = servicesForResolution.specialise(ltx)
|
||||
|
||||
// Do nothing unless we have Corda's deterministic libraries.
|
||||
val cordaSource = djvmCordaSource ?: return ledgerTransaction
|
||||
djvmCordaSource ?: return ledgerTransaction
|
||||
|
||||
// Specialise the LedgerTransaction here so that
|
||||
// contracts are verified inside the DJVM!
|
||||
return ledgerTransaction.specialise { tx, cl ->
|
||||
(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
|
||||
)
|
||||
)
|
||||
return ledgerTransaction.specialise(deterministicVerifierFactoryService::specialise)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
))
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -1,15 +1,19 @@
|
||||
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.crypto.SecureHash
|
||||
import net.corda.core.internal.ContractVerifier
|
||||
import net.corda.core.internal.Verifier
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.djvm.SandboxConfiguration
|
||||
import net.corda.djvm.analysis.AnalysisConfiguration
|
||||
import net.corda.djvm.execution.*
|
||||
import net.corda.djvm.messages.Message
|
||||
import net.corda.djvm.source.ClassSource
|
||||
import net.corda.node.djvm.LtxFactory
|
||||
|
||||
class DeterministicVerifier(
|
||||
ltx: LedgerTransaction,
|
||||
@ -23,24 +27,67 @@ class DeterministicVerifier(
|
||||
profile = ExecutionProfile.DEFAULT,
|
||||
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 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...
|
||||
executor.execute(verifier, sandboxTx)
|
||||
verifier.apply(sandboxTx)
|
||||
}
|
||||
|
||||
result.exception?.run {
|
||||
val sandboxEx = SandboxException(
|
||||
Message.getMessageFromException(this),
|
||||
result.identifier,
|
||||
verifierClass,
|
||||
ClassSource.fromClassName(ContractVerifier::class.java.name),
|
||||
ExecutionSummary(result.costs),
|
||||
this
|
||||
)
|
||||
@ -51,7 +98,7 @@ class DeterministicVerifier(
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun close() {
|
||||
analysisConfiguration.closeAll()
|
||||
// analysisConfiguration.closeAll()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ include 'docs'
|
||||
include 'node-api'
|
||||
include 'node'
|
||||
include 'node:capsule'
|
||||
include 'node:djvm'
|
||||
include 'client:jackson'
|
||||
include 'client:jfx'
|
||||
include 'client:mock'
|
||||
|
Loading…
Reference in New Issue
Block a user