Contract constraints (#1518)

* Contract constraints and attachment loading

Fix compiler warnings.

Fixed IdentitySyncFlowTests in confidential-identities.

Fixes.

Fix AttachmentClassLoaderTests.

Added a TODO.

Renamed cordapp service.

Fix compilation error in java code.

Fix RaftNotaryServiceTests

Fix AttachmentLoadingTest

Fix DistributedServiceTests and LargeTransactionTests.

Add cordapp packages to Verifier tests.

Refactor DummyContractBackdoor back out of internal package.

Resolve compiler warnings.

Consolidate excluding `isolated` project at top-level.

Fix contract attachment serialisation for remote verifier.

Fix integration tests for client:rpc.

Contract constraints and attachment loading

Fix compiler warnings.

Fixed IdentitySyncFlowTests in confidential-identities.

Fixes.

Fix AttachmentClassLoaderTests.

Added a TODO.

Renamed cordapp service.

Fix compilation error in java code.

Fix example compilation.

Fix RaftNotaryServiceTests

Fix AttachmentLoadingTest

Fix DistributedServiceTests and LargeTransactionTests.

Add cordapp packages to Verifier tests.

Refactor DummyContractBackdoor back out of internal package.

Resolve compiler warnings.

Consolidate excluding `isolated` project at top-level.

Fix integration tests for client:rpc.

Fixed issues with node driver and differing ZIPs.

Review changes.

Refactor GeneratedAttachment into node-api module.

Merge branch 'clint/hash-constraint' of https://github.com/corda/corda into clint/hash-constraint

Fixed compile error following rebase.

wip - test to check that app code isn't loaded from attachments sent over the wire.

Use Kotlin copyTo() rather than Apache's IOUtils.

Fixes

more fixes.

Removing unconstrained output.

More fixes.

Fixed another test.

Added missing plugin definition in net.corda.core.node.CordaPluginRegistry: net.corda.finance.contracts.isolated.IsolatedPlugin

Re-added missing magic string used in unit test.

Remove unused FlowSession variable.

* Review fixes.

* More review fixes.

* Moved Cordapp implementation to an internal package.

* More JVMOverloads.
This commit is contained in:
Clinton 2017-09-25 17:05:18 +01:00 committed by josecoll
parent 2a7da1eb47
commit 532bbb5cca
116 changed files with 1601 additions and 599 deletions

View File

@ -1,21 +1,25 @@
package net.corda.client.jackson
import com.fasterxml.jackson.databind.SerializationFeature
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.Amount
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.*
import net.corda.finance.USD
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.generateKeyPair
import net.corda.core.node.ServiceHub
import net.corda.core.transactions.SignedTransaction
import net.corda.testing.ALICE_PUBKEY
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.MINI_CORP
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.contracts.DUMMY_PROGRAM_ID
import net.corda.testing.contracts.DummyContract
import net.i2p.crypto.eddsa.EdDSAPublicKey
import org.junit.Before
import org.junit.Test
import java.util.*
import kotlin.reflect.jvm.jvmName
import kotlin.test.assertEquals
class JacksonSupportTest : TestDependencyInjectionBase() {
@ -23,6 +27,16 @@ class JacksonSupportTest : TestDependencyInjectionBase() {
val mapper = JacksonSupport.createNonRpcMapper()
}
private lateinit var services: ServiceHub
private lateinit var cordappProvider: CordappProvider
@Before
fun setup() {
services = mock()
cordappProvider = mock()
whenever(services.cordappProvider).thenReturn(cordappProvider)
}
@Test
fun publicKeySerializingWorks() {
val publicKey = generateKeyPair().public
@ -57,8 +71,12 @@ class JacksonSupportTest : TestDependencyInjectionBase() {
@Test
fun writeTransaction() {
val attachmentRef = SecureHash.randomSHA256()
whenever(cordappProvider.getContractAttachmentID(DUMMY_PROGRAM_ID))
.thenReturn(attachmentRef)
fun makeDummyTx(): SignedTransaction {
val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1)).toWireTransaction()
val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1))
.toWireTransaction(services)
val signatures = TransactionSignature(
ByteArray(1),
ALICE_PUBKEY,

View File

@ -25,14 +25,14 @@ import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutionException;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static java.util.Collections.*;
import static java.util.Objects.requireNonNull;
import static kotlin.test.AssertionsKt.assertEquals;
import static net.corda.client.rpc.CordaRPCClientConfiguration.getDefault;
import static net.corda.finance.Currencies.DOLLARS;
import static net.corda.finance.contracts.GetBalances.getCashBalance;
import static net.corda.node.services.FlowPermissions.startFlowPermission;
import static net.corda.testing.CoreTestUtils.*;
import static net.corda.testing.TestConstants.getALICE;
public class CordaRPCJavaClientTest extends NodeBasedTest {
@ -52,6 +52,7 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
@Before
public void setUp() throws ExecutionException, InterruptedException {
setCordappPackages("net.corda.finance.contracts");
Set<ServiceInfo> services = new HashSet<>(singletonList(new ServiceInfo(ValidatingNotaryService.Companion.getType(), null)));
CordaFuture<StartedNode<Node>> nodeFuture = startNode(getALICE().getName(), 1, services, singletonList(rpcUser), emptyMap());
node = nodeFuture.get();
@ -62,6 +63,7 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
@After
public void done() throws IOException {
connection.close();
unsetCordappPackages();
}
@Test

View File

@ -25,6 +25,8 @@ import net.corda.nodeapi.User
import net.corda.testing.ALICE
import net.corda.testing.chooseIdentity
import net.corda.testing.node.NodeBasedTest
import net.corda.testing.setCordappPackages
import net.corda.testing.unsetCordappPackages
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.After
@ -49,6 +51,7 @@ class CordaRPCClientTest : NodeBasedTest() {
@Before
fun setUp() {
setCordappPackages("net.corda.finance.contracts")
node = startNode(ALICE.name, rpcUsers = listOf(rpcUser), advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type))).getOrThrow()
node.internals.registerCustomSchemas(setOf(CashSchemaV1))
client = CordaRPCClient(node.internals.configuration.rpcAddress!!, initialiseSerialization = false)
@ -57,6 +60,7 @@ class CordaRPCClientTest : NodeBasedTest() {
@After
fun done() {
connection?.close()
unsetCordappPackages()
}
@Test

View File

@ -26,6 +26,7 @@ class IdentitySyncFlowTests {
@Before
fun before() {
setCordappPackages("net.corda.finance.contracts.asset")
// We run this in parallel threads to help catch any race conditions that may exist.
mockNet = MockNetwork(networkSendManuallyPumped = false, threadPerNode = true)
}
@ -33,6 +34,7 @@ class IdentitySyncFlowTests {
@After
fun cleanUp() {
mockNet.stopNodes()
unsetCordappPackages()
}
@Test

View File

@ -1,15 +1,37 @@
package net.corda.core.contracts
import net.corda.core.crypto.SecureHash
import net.corda.core.serialization.CordaSerializable
/** Constrain which contract-code-containing attachments can be used with a [ContractState]. */
/** Constrain which contract-code-containing attachment can be used with a [ContractState]. */
@CordaSerializable
interface AttachmentConstraint {
/** Returns whether the given contract attachments can be used with the [ContractState] associated with this constraint object. */
fun isSatisfiedBy(attachments: List<Attachment>): Boolean
/** Returns whether the given contract attachment can be used with the [ContractState] associated with this constraint object. */
fun isSatisfiedBy(attachment: Attachment): Boolean
}
/** An [AttachmentConstraint] where [isSatisfiedBy] always returns true. */
object AlwaysAcceptAttachmentConstraint : AttachmentConstraint {
override fun isSatisfiedBy(attachments: List<Attachment>) = true
override fun isSatisfiedBy(attachment: Attachment) = true
}
/** An [AttachmentConstraint] that verifies by hash */
data class HashAttachmentConstraint(val attachmentId: SecureHash) : AttachmentConstraint {
override fun isSatisfiedBy(attachment: Attachment) = attachment.id == attachmentId
}
/**
* This [AttachmentConstraint] is a convenience class that will be automatically resolved to a [HashAttachmentConstraint].
* The resolution occurs in [TransactionBuilder.toWireTransaction] and uses the [TransactionState.contract] value
* to find a corresponding loaded [Cordapp] that contains such a contract, and then uses that [Cordapp] as the
* [Attachment].
*
* If, for any reason, this class is not automatically resolved the default implementation is to fail, because the
* intent of this class is that it should be replaced by a correct [HashAttachmentConstraint] and verify against an
* actual [Attachment].
*/
object AutomaticHashConstraint : AttachmentConstraint {
override fun isSatisfiedBy(attachment: Attachment): Boolean {
throw UnsupportedOperationException("Contracts cannot be satisfied by an AutomaticHashConstraint placeholder")
}
}

View File

@ -15,43 +15,16 @@ data class TransactionState<out T : ContractState> @JvmOverloads constructor(
/** The custom contract state */
val data: T,
/**
* An instance of the contract class that will verify this state.
* The contract class name that will verify this state that will be created via reflection.
* The attachment containing this class will be automatically added to the transaction at transaction creation
* time.
*
* # Discussion
* Currently these are loaded from the classpath of the node which includes the cordapp directory - at some
* point these will also be loaded and run from the attachment store directly, allowing contracts to be
* sent across, and run, from the network from within a sandbox environment.
*
* This field is not the final design, it's just a piece of temporary scaffolding. Once the contract sandbox is
* further along, this field will become a description of which attachments are acceptable for defining the
* contract.
*
* Recall that an attachment is a zip file that can be referenced from any transaction. The contents of the
* attachments are merged together and cannot define any overlapping files, thus for any given transaction there
* is a miniature file system in which each file can be precisely mapped to the defining attachment.
*
* Attachments may contain many things (data files, legal documents, etc) but mostly they contain JVM bytecode.
* The class files inside define not only [Contract] implementations but also the classes that define the states.
* Within the rest of a transaction, user-providable components are referenced by name only.
*
* This means that a smart contract in Corda does two things:
*
* 1. Define the data structures that compose the ledger (the states)
* 2. Define the rules for updating those structures
*
* The first is merely a utility role ... in theory contract code could manually parse byte streams by hand.
* The second is vital to the integrity of the ledger. So this field needs to be able to express constraints like:
*
* - Only attachment 733c350f396a727655be1363c06635ba355036bd54a5ed6e594fd0b5d05f42f6 may be used with this state.
* - Any attachment signed by public key 2d1ce0e330c52b8055258d776c40 may be used with this state.
* - Attachments (1, 2, 3) may all be used with this state.
*
* and so on. In this way it becomes possible for the business logic governing a state to be evolved, if the
* constraints are flexible enough.
*
* Because contract classes often also define utilities that generate relevant transactions, and because attachments
* cannot know their own hashes, we will have to provide various utilities to assist with obtaining the right
* code constraints from within the contract code itself.
*
* TODO: Implement the above description. See COR-226
*/
* TODO: Implement the contract sandbox loading of the contract attachments
* */
val contract: ContractClassName,
/** Identity of the notary that ensures the state is not used as an input to a transaction more than once */
val notary: Party,
@ -76,5 +49,5 @@ data class TransactionState<out T : ContractState> @JvmOverloads constructor(
/**
* A validator for the contract attachments on the transaction.
*/
val constraint: AttachmentConstraint = AlwaysAcceptAttachmentConstraint)
val constraint: AttachmentConstraint = AutomaticHashConstraint)
// DOCEND 1

View File

@ -0,0 +1,33 @@
package net.corda.core.cordapp
import net.corda.core.flows.FlowLogic
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializeAsToken
import java.net.URL
/**
* Represents a cordapp by registering the JAR that contains it and all important classes for Corda.
* Instances of this class are generated automatically at startup of a node and can get retrieved from
* [CordappProvider.getAppContext] from the [CordappContext] it returns.
*
* This will only need to be constructed manually for certain kinds of tests.
*
* @property contractClassNames List of contracts
* @property initiatedFlows List of initiatable flow classes
* @property rpcFlows List of RPC initiable flows classes
* @property servies List of RPC services
* @property plugins List of Corda plugin registries
* @property customSchemas List of custom schemas
* @property jarPath The path to the JAR for this CorDapp
*/
interface Cordapp {
val contractClassNames: List<String>
val initiatedFlows: List<Class<out FlowLogic<*>>>
val rpcFlows: List<Class<out FlowLogic<*>>>
val services: List<Class<out SerializeAsToken>>
val plugins: List<CordaPluginRegistry>
val customSchemas: Set<MappedSchema>
val jarPath: URL
val cordappClasses: List<String>
}

View File

@ -0,0 +1,19 @@
package net.corda.core.cordapp
import net.corda.core.crypto.SecureHash
// TODO: Add per app config
/**
* An app context provides information about where an app was loaded from, access to its classloader,
* and (in the included [Cordapp] object) lists of annotated classes discovered via scanning the JAR.
*
* A CordappContext is obtained from [CordappProvider.getAppContext] which resides on a [ServiceHub]. This will be
* used primarily from within flows.
*
* @property cordapp The cordapp this context is about
* @property attachmentId For CorDapps containing [Contract] or [UpgradedContract] implementations this will be populated
* with the attachment containing those class files
* @property classLoader the classloader used to load this cordapp's classes
*/
class CordappContext(val cordapp: Cordapp, val attachmentId: SecureHash?, val classLoader: ClassLoader)

View File

@ -0,0 +1,28 @@
package net.corda.core.cordapp
import net.corda.core.contracts.ContractClassName
import net.corda.core.node.services.AttachmentId
/**
* Provides access to what the node knows about loaded applications.
*/
interface CordappProvider {
/**
* Exposes the current CorDapp context which will contain information and configuration of the CorDapp that
* is currently running.
*
* The calling application is found via stack walking and finding the first class on the stack that matches any class
* contained within the automatically resolved [Cordapp]s loaded by the [CordappLoader]
*
* @throws IllegalStateException When called from a non-app context
*/
fun getAppContext(): CordappContext
/**
* Resolve an attachment ID for a given contract name
*
* @param contractClassName The contract to find the attachment for
* @return An attachment ID if it exists
*/
fun getContractAttachmentID(contractClassName: ContractClassName): AttachmentId?
}

View File

@ -4,6 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.*
import net.corda.core.internal.ContractUpgradeUtils
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import java.security.PublicKey
/**
@ -88,6 +89,23 @@ object ContractUpgradeFlow {
newContractClass: Class<out UpgradedContract<OldState, NewState>>
) : AbstractStateReplacementFlow.Instigator<OldState, NewState, Class<out UpgradedContract<OldState, NewState>>>(originalState, newContractClass) {
companion object {
fun <OldState : ContractState, NewState : ContractState> assembleBareTx(
stateRef: StateAndRef<OldState>,
upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>,
privacySalt: PrivacySalt
): TransactionBuilder {
val contractUpgrade = upgradedContractClass.newInstance()
return TransactionBuilder(stateRef.state.notary)
.withItems(
stateRef,
StateAndContract(contractUpgrade.upgrade(stateRef.state.data), upgradedContractClass.name),
Command(UpgradeCommand(upgradedContractClass.name), stateRef.state.data.participants.map { it.owningKey }),
privacySalt
)
}
}
@Suspendable
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
val baseTx = ContractUpgradeUtils.assembleBareTx(originalState, modification, PrivacySalt())

View File

@ -24,7 +24,6 @@ abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment {
/** @see <https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File> */
private val unsignableEntryName = "META-INF/(?:.*[.](?:SF|DSA|RSA)|SIG-.*)".toRegex()
private val shredder = ByteArray(1024)
}
protected val attachmentData: ByteArray by lazy(dataLoader)
@ -33,6 +32,7 @@ abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment {
// Can't start with empty set if we're doing intersections. Logically the null means "all possible signers":
var attachmentSigners: MutableSet<CodeSigner>? = null
openAsJAR().use { jar ->
val shredder = ByteArray(1024)
while (true) {
val entry = jar.nextJarEntry ?: break
if (entry.isDirectory || unsignableEntryName.matches(entry.name)) continue

View File

@ -0,0 +1,24 @@
package net.corda.core.internal.cordapp
import net.corda.core.cordapp.Cordapp
import net.corda.core.flows.FlowLogic
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializeAsToken
import java.net.URL
data class CordappImpl(
override val contractClassNames: List<String>,
override val initiatedFlows: List<Class<out FlowLogic<*>>>,
override val rpcFlows: List<Class<out FlowLogic<*>>>,
override val services: List<Class<out SerializeAsToken>>,
override val plugins: List<CordaPluginRegistry>,
override val customSchemas: Set<MappedSchema>,
override val jarPath: URL) : Cordapp {
/**
* An exhaustive list of all classes relevant to the node within this CorDapp
*
* TODO: Also add [SchedulableFlow] as a Cordapp class
*/
override val cordappClasses = ((rpcFlows + initiatedFlows + services + plugins.map { javaClass }).map { it.name } + contractClassNames)
}

View File

@ -1,6 +1,7 @@
package net.corda.core.node
import net.corda.core.contracts.*
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata
@ -30,6 +31,9 @@ interface ServicesForResolution {
/** Provides access to storage of arbitrary JAR files (which may contain only data, no code). */
val attachments: AttachmentStorage
/** Provides access to anything relating to cordapps including contract attachment resolution and app context */
val cordappProvider: CordappProvider
/**
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
*
@ -127,9 +131,9 @@ interface ServiceHub : ServicesForResolution {
fun <T : ContractState> toStateAndRef(stateRef: StateRef): StateAndRef<T> {
val stx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
return if (stx.isNotaryChangeTransaction()) {
stx.resolveNotaryChangeTransaction(this).outRef<T>(stateRef.index)
stx.resolveNotaryChangeTransaction(this).outRef(stateRef.index)
} else {
stx.tx.outRef<T>(stateRef.index)
stx.tx.outRef(stateRef.index)
}
}
@ -137,7 +141,7 @@ interface ServiceHub : ServicesForResolution {
// Helper method to construct an initial partially signed transaction from a [TransactionBuilder].
private fun signInitialTransaction(builder: TransactionBuilder, publicKey: PublicKey, signatureMetadata: SignatureMetadata): SignedTransaction {
return builder.toSignedTransaction(keyManagementService, publicKey, signatureMetadata)
return builder.toSignedTransaction(keyManagementService, publicKey, signatureMetadata, this)
}
/**

View File

@ -61,7 +61,29 @@ data class LedgerTransaction(
* @throws TransactionVerificationException if anything goes wrong.
*/
@Throws(TransactionVerificationException::class)
fun verify() = verifyContracts()
fun verify() {
verifyConstraints()
verifyContracts()
}
/**
* Verify that all contract constraints are valid for each state before running any contract code
*
* @throws TransactionVerificationException if the constraints fail to verify
*/
private fun verifyConstraints() {
val contractAttachments = attachments.filterIsInstance<ContractAttachment>()
(inputs.map { it.state } + outputs).forEach { state ->
// Ordering of attachments matters - if two attachments contain the same named contract then the second
// will be shadowed by the first.
val contractAttachment = contractAttachments.find { it.contract == state.contract }
?: throw TransactionVerificationException.MissingAttachmentRejection(id, state.contract)
if (!state.constraint.isSatisfiedBy(contractAttachment)) {
throw TransactionVerificationException.ContractConstraintRejection(id, state.contract)
}
}
}
/**
* Check the transaction is contract-valid by running the verify() for each input and output state contract.
@ -71,14 +93,15 @@ data class LedgerTransaction(
val contracts = (inputs.map { it.state.contract } + outputs.map { it.contract }).toSet()
for (contractClassName in contracts) {
val contract = try {
assert(javaClass.classLoader == ClassLoader.getSystemClassLoader())
javaClass.classLoader.loadClass(contractClassName).asSubclass(Contract::class.java).getConstructor().newInstance()
} catch(e: ClassNotFoundException) {
} catch (e: ClassNotFoundException) {
throw TransactionVerificationException.ContractCreationError(id, contractClassName, e)
}
try {
contract.verify(this)
} catch(e: Throwable) {
} catch (e: Throwable) {
throw TransactionVerificationException.ContractRejection(id, contract, e)
}
}

View File

@ -155,6 +155,11 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
}
}
/**
* TODO: Verify contract constraints here as well as in LedgerTransaction to ensure that anything being deserialised
* from the attachment is trusted. This will require some partial serialisation work to not load the ContractState
* objects from the TransactionState.
*/
private fun verifyRegularTransaction(checkSufficientSignatures: Boolean, services: ServiceHub) {
checkSignaturesAreValid()
if (checkSufficientSignatures) verifyRequiredSignatures()

View File

@ -8,8 +8,8 @@ import net.corda.core.crypto.SignatureMetadata
import net.corda.core.identity.Party
import net.corda.core.internal.FlowStateMachine
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.KeyManagementService
import java.lang.UnsupportedOperationException
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationFactory
import java.security.PublicKey
@ -75,12 +75,35 @@ open class TransactionBuilder(
}
// DOCEND 1
fun toWireTransaction(serializationContext: SerializationContext? = null) = SerializationFactory.defaultFactory.withCurrentContext(serializationContext) {
WireTransaction(WireTransaction.createComponentGroups(inputs, outputs, commands, attachments, notary, window), privacySalt)
/**
* Generates a [WireTransaction] from this builder and resolves any [AutomaticHashConstraint] on contracts to
* [HashAttachmentConstraint].
*
* @returns A new [WireTransaction] that will be unaffected by further changes to this [TransactionBuilder].
*/
@JvmOverloads
@Throws(MissingContractAttachments::class)
fun toWireTransaction(services: ServicesForResolution, serializationContext: SerializationContext? = null): WireTransaction {
// Resolves the AutomaticHashConstraints to HashAttachmentConstraints for convenience. The AutomaticHashConstraint
// allows for less boiler plate when constructing transactions since for the typical case the named contract
// will be available when building the transaction. In exceptional cases the TransactionStates must be created
// with an explicit [AttachmentConstraint]
val resolvedOutputs = outputs.map { state ->
if (state.constraint is AutomaticHashConstraint) {
services.cordappProvider.getContractAttachmentID(state.contract)?.let {
state.copy(constraint = HashAttachmentConstraint(it))
} ?: throw MissingContractAttachments(listOf(state))
} else {
state
}
}
return SerializationFactory.defaultFactory.withCurrentContext(serializationContext) {
WireTransaction(WireTransaction.createComponentGroups(inputs, resolvedOutputs, commands, attachments, notary, window), privacySalt)
}
}
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServiceHub) = toWireTransaction().toLedgerTransaction(services)
fun toLedgerTransaction(services: ServiceHub, serializationContext: SerializationContext? = null) = toWireTransaction(services, serializationContext).toLedgerTransaction(services)
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class)
fun verify(services: ServiceHub) {
@ -99,20 +122,22 @@ open class TransactionBuilder(
return this
}
@JvmOverloads
fun addOutputState(state: TransactionState<*>): TransactionBuilder {
outputs.add(state)
return this
}
@JvmOverloads
fun addOutputState(state: ContractState, contract: ContractClassName, notary: Party, encumbrance: Int? = null): TransactionBuilder {
return addOutputState(TransactionState(state, contract, notary, encumbrance))
fun addOutputState(state: ContractState, contract: ContractClassName, notary: Party, encumbrance: Int? = null, constraint: AttachmentConstraint = AutomaticHashConstraint): TransactionBuilder {
return addOutputState(TransactionState(state, contract, notary, encumbrance, constraint))
}
/** A default notary must be specified during builder construction to use this method */
fun addOutputState(state: ContractState, contract: ContractClassName): TransactionBuilder {
@JvmOverloads
fun addOutputState(state: ContractState, contract: ContractClassName, constraint: AttachmentConstraint = AutomaticHashConstraint): TransactionBuilder {
checkNotNull(notary) { "Need to specify a notary for the state, or set a default one on TransactionBuilder initialisation" }
addOutputState(state, contract, notary!!)
addOutputState(state, contract, notary!!, constraint = constraint)
return this
}
@ -159,10 +184,10 @@ open class TransactionBuilder(
* Sign the built transaction and return it. This is an internal function for use by the service hub, please use
* [ServiceHub.signInitialTransaction] instead.
*/
fun toSignedTransaction(keyManagementService: KeyManagementService, publicKey: PublicKey, signatureMetadata: SignatureMetadata): SignedTransaction {
val wtx = toWireTransaction()
fun toSignedTransaction(keyManagementService: KeyManagementService, publicKey: PublicKey, signatureMetadata: SignatureMetadata, services: ServicesForResolution): SignedTransaction {
val wtx = toWireTransaction(services)
val signableData = SignableData(wtx.id, signatureMetadata)
val sig = keyManagementService.sign(signableData, publicKey)
return SignedTransaction(wtx, listOf(sig))
}
}
}

View File

@ -8,6 +8,7 @@ import net.corda.core.internal.Emoji
import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.*
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.node.services.AttachmentId
import java.security.PublicKey
import java.security.SignatureException
import java.util.function.Predicate
@ -83,8 +84,9 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
fun toLedgerTransaction(services: ServicesForResolution): LedgerTransaction {
return toLedgerTransaction(
resolveIdentity = { services.identityService.partyFromKey(it) },
resolveAttachment = { services.attachments.openAttachment(it) },
resolveStateRef = { services.loadState(it) }
resolveAttachment = { services.attachments.openAttachment(it)},
resolveStateRef = { services.loadState(it) },
resolveContractAttachment = { services.cordappProvider.getContractAttachmentID(it.contract) }
)
}
@ -99,18 +101,20 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
fun toLedgerTransaction(
resolveIdentity: (PublicKey) -> Party?,
resolveAttachment: (SecureHash) -> Attachment?,
resolveStateRef: (StateRef) -> TransactionState<*>?
resolveStateRef: (StateRef) -> TransactionState<*>?,
resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId?
): LedgerTransaction {
// Look up public keys to authenticated identities. This is just a stub placeholder and will all change in future.
val authenticatedArgs = commands.map {
val parties = it.signers.mapNotNull { pk -> resolveIdentity(pk) }
CommandWithParties(it.signers, parties, it.value)
}
// Open attachments specified in this transaction. If we haven't downloaded them, we fail.
val attachments = attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) }
val resolvedInputs = inputs.map { ref ->
resolveStateRef(ref)?.let { StateAndRef(it, ref) } ?: throw TransactionResolutionException(ref.txhash)
}
// Open attachments specified in this transaction. If we haven't downloaded them, we fail.
val contractAttachments = findAttachmentContracts(resolvedInputs, resolveContractAttachment, resolveAttachment)
val attachments = contractAttachments + (attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) }).distinct()
return LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt)
}
@ -225,6 +229,19 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
return buf.toString()
}
private fun findAttachmentContracts(resolvedInputs: List<StateAndRef<ContractState>>,
resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId?,
resolveAttachment: (SecureHash) -> Attachment?
): List<Attachment> {
val contractAttachments = (outputs + resolvedInputs.map { it.state }).map { Pair(it, resolveContractAttachment(it)) }
val missingAttachments = contractAttachments.filter { it.second == null }
return if(missingAttachments.isEmpty()) {
contractAttachments.map { ContractAttachment(resolveAttachment(it.second!!) ?: throw AttachmentResolutionException(it.second!!), it.first.contract) }
} else {
throw MissingContractAttachments(missingAttachments.map { it.first })
}
}
override fun equals(other: Any?): Boolean {
if (other is WireTransaction) {
return (this.id == other.id)

View File

@ -1,13 +1,20 @@
package net.corda.core.contracts
import com.nhaarman.mockito_kotlin.mock
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyContractV2
import net.corda.core.crypto.SecureHash
import net.corda.core.node.ServicesForResolution
import net.corda.testing.ALICE
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.contracts.DUMMY_PROGRAM_ID
import net.corda.testing.contracts.DUMMY_V2_PROGRAM_ID
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.node.MockServices
import net.corda.testing.setCordappPackages
import net.corda.testing.unsetCordappPackages
import org.junit.After
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@ -18,15 +25,16 @@ import kotlin.test.assertTrue
class DummyContractV2Tests : TestDependencyInjectionBase() {
@Test
fun `upgrade from v1`() {
val services = MockServices()
val contractUpgrade = DummyContractV2()
val v1State = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DUMMY_PROGRAM_ID, DUMMY_NOTARY)
val v1State = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DUMMY_PROGRAM_ID, DUMMY_NOTARY, constraint = AlwaysAcceptAttachmentConstraint)
val v1Ref = StateRef(SecureHash.randomSHA256(), 0)
val v1StateAndRef = StateAndRef(v1State, v1Ref)
val (tx, _) = DummyContractV2().generateUpgradeFromV1(v1StateAndRef)
val (tx, _) = DummyContractV2().generateUpgradeFromV1(services, v1StateAndRef)
assertEquals(v1Ref, tx.inputs.single())
val expectedOutput = TransactionState(contractUpgrade.upgrade(v1State.data), DUMMY_V2_PROGRAM_ID, DUMMY_NOTARY)
val expectedOutput = TransactionState(contractUpgrade.upgrade(v1State.data), DUMMY_V2_PROGRAM_ID, DUMMY_NOTARY, constraint = AlwaysAcceptAttachmentConstraint)
val actualOutput = tx.outputs.single()
assertEquals(expectedOutput, actualOutput)

View File

@ -1,14 +1,12 @@
package net.corda.core.contracts
import net.corda.core.identity.AbstractParty
import net.corda.core.node.ServiceHub
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.contracts.DUMMY_PROGRAM_ID
import net.corda.testing.chooseIdentity
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DUMMY_PROGRAM_ID
import net.corda.testing.dummyCommand
import net.corda.testing.node.MockServices
import org.junit.Before
@ -20,11 +18,11 @@ import kotlin.test.assertTrue
class LedgerTransactionQueryTests : TestDependencyInjectionBase() {
private lateinit var services: ServiceHub
private val services: MockServices = MockServices()
@Before
fun setup() {
services = MockServices()
services.mockCordappProvider.addMockCordapp(DUMMY_PROGRAM_ID, services)
}
interface Commands {

View File

@ -48,6 +48,7 @@ class TransactionEncumbranceTests {
fun `state can be encumbered`() {
ledger {
transaction {
attachments(CASH_PROGRAM_ID, TEST_TIMELOCK_ID)
input(CASH_PROGRAM_ID) { state }
output(CASH_PROGRAM_ID, encumbrance = 1) { stateWithNewOwner }
output(TEST_TIMELOCK_ID, "5pm time-lock") { timeLock }
@ -61,11 +62,13 @@ class TransactionEncumbranceTests {
fun `state can transition if encumbrance rules are met`() {
ledger {
unverifiedTransaction {
attachments(CASH_PROGRAM_ID, TEST_TIMELOCK_ID)
output(CASH_PROGRAM_ID, "state encumbered by 5pm time-lock") { state }
output(TEST_TIMELOCK_ID, "5pm time-lock") { timeLock }
}
// Un-encumber the output if the time of the transaction is later than the timelock.
transaction {
attachments(CASH_PROGRAM_ID, TEST_TIMELOCK_ID)
input("state encumbered by 5pm time-lock")
input("5pm time-lock")
output(CASH_PROGRAM_ID) { stateWithNewOwner }
@ -80,11 +83,13 @@ class TransactionEncumbranceTests {
fun `state cannot transition if the encumbrance contract fails to verify`() {
ledger {
unverifiedTransaction {
attachments(CASH_PROGRAM_ID, TEST_TIMELOCK_ID)
output(CASH_PROGRAM_ID, "state encumbered by 5pm time-lock") { state }
output(TEST_TIMELOCK_ID, "5pm time-lock") { timeLock }
}
// The time of the transaction is earlier than the time specified in the encumbering timelock.
transaction {
attachments(CASH_PROGRAM_ID, TEST_TIMELOCK_ID)
input("state encumbered by 5pm time-lock")
input("5pm time-lock")
output(CASH_PROGRAM_ID) { state }
@ -99,10 +104,12 @@ class TransactionEncumbranceTests {
fun `state must be consumed along with its encumbrance`() {
ledger {
unverifiedTransaction {
attachments(CASH_PROGRAM_ID, TEST_TIMELOCK_ID)
output(CASH_PROGRAM_ID, "state encumbered by 5pm time-lock", encumbrance = 1) { state }
output(TEST_TIMELOCK_ID, "5pm time-lock") { timeLock }
}
transaction {
attachments(CASH_PROGRAM_ID)
input("state encumbered by 5pm time-lock")
output(CASH_PROGRAM_ID) { stateWithNewOwner }
command(MEGA_CORP.owningKey) { Cash.Commands.Move() }
@ -116,6 +123,7 @@ class TransactionEncumbranceTests {
fun `state cannot be encumbered by itself`() {
ledger {
transaction {
attachments(CASH_PROGRAM_ID)
input(CASH_PROGRAM_ID) { state }
output(CASH_PROGRAM_ID, encumbrance = 0) { stateWithNewOwner }
command(MEGA_CORP.owningKey) { Cash.Commands.Move() }
@ -128,6 +136,7 @@ class TransactionEncumbranceTests {
fun `encumbrance state index must be valid`() {
ledger {
transaction {
attachments(CASH_PROGRAM_ID, TEST_TIMELOCK_ID)
input(CASH_PROGRAM_ID) { state }
output(TEST_TIMELOCK_ID, encumbrance = 2) { stateWithNewOwner }
output(TEST_TIMELOCK_ID) { timeLock }
@ -141,11 +150,13 @@ class TransactionEncumbranceTests {
fun `correct encumbrance state must be provided`() {
ledger {
unverifiedTransaction {
attachments(CASH_PROGRAM_ID, TEST_TIMELOCK_ID)
output(CASH_PROGRAM_ID, "state encumbered by some other state", encumbrance = 1) { state }
output(CASH_PROGRAM_ID, "some other state") { state }
output(TEST_TIMELOCK_ID, "5pm time-lock") { timeLock }
}
transaction {
attachments(CASH_PROGRAM_ID, TEST_TIMELOCK_ID)
input("state encumbered by some other state")
input("5pm time-lock")
output(CASH_PROGRAM_ID) { stateWithNewOwner }

View File

@ -10,6 +10,8 @@ import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_KEY
import net.corda.testing.*
import net.corda.testing.contracts.DUMMY_PROGRAM_ID
import net.corda.testing.contracts.DummyContract
import net.corda.testing.node.MockAttachment
import net.corda.testing.node.MockAttachmentStorage
import org.junit.Test
import java.security.KeyPair
import kotlin.test.assertEquals
@ -94,11 +96,11 @@ class TransactionTests : TestDependencyInjectionBase() {
@Test
fun `transactions with no inputs can have any notary`() {
val baseOutState = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DUMMY_PROGRAM_ID, DUMMY_NOTARY)
val baseOutState = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DUMMY_PROGRAM_ID, DUMMY_NOTARY, constraint = AlwaysAcceptAttachmentConstraint)
val inputs = emptyList<StateAndRef<*>>()
val outputs = listOf(baseOutState, baseOutState.copy(notary = ALICE), baseOutState.copy(notary = BOB))
val commands = emptyList<CommandWithParties<CommandData>>()
val attachments = emptyList<Attachment>()
val attachments = listOf<Attachment>(ContractAttachment(MockAttachment(), DUMMY_PROGRAM_ID))
val id = SecureHash.randomSHA256()
val timeWindow: TimeWindow? = null
val privacySalt: PrivacySalt = PrivacySalt()

View File

@ -32,6 +32,7 @@ class PartialMerkleTreeTest : TestDependencyInjectionBase() {
private val testLedger = ledger {
unverifiedTransaction {
attachments(CASH_PROGRAM_ID)
output(CASH_PROGRAM_ID, "MEGA_CORP cash") {
Cash.State(
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
@ -47,6 +48,7 @@ class PartialMerkleTreeTest : TestDependencyInjectionBase() {
}
transaction {
attachments(CASH_PROGRAM_ID)
input("MEGA_CORP cash")
output(CASH_PROGRAM_ID, "MEGA_CORP cash".output<Cash.State>().copy(owner = MINI_CORP))
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }

View File

@ -10,9 +10,7 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap
import net.corda.node.internal.StartedNode
import net.corda.testing.MINI_CORP_KEY
import net.corda.testing.chooseIdentity
import net.corda.testing.chooseIdentityAndCert
import net.corda.testing.*
import net.corda.testing.contracts.DUMMY_PROGRAM_ID
import net.corda.testing.contracts.DummyContract
import net.corda.testing.getDefaultNotary
@ -30,9 +28,12 @@ class CollectSignaturesFlowTests {
lateinit var b: StartedNode<MockNetwork.MockNode>
lateinit var c: StartedNode<MockNetwork.MockNode>
lateinit var notary: Party
lateinit var services: MockServices
@Before
fun setup() {
setCordappPackages("net.corda.testing.contracts")
services = MockServices()
mockNet = MockNetwork()
val nodes = mockNet.createSomeNodes(3)
a = nodes.partyNodes[0]
@ -46,6 +47,7 @@ class CollectSignaturesFlowTests {
@After
fun tearDown() {
mockNet.stopNodes()
unsetCordappPackages()
}
private fun registerFlowOnAllNodes(flowClass: KClass<out FlowLogic<*>>) {

View File

@ -41,6 +41,7 @@ class ContractUpgradeFlowTest {
@Before
fun setup() {
setCordappPackages("net.corda.testing.contracts", "net.corda.finance.contracts.asset", "net.corda.core.flows")
mockNet = MockNetwork()
val nodes = mockNet.createSomeNodes(notaryKeyPair = null) // prevent generation of notary override
a = nodes.partyNodes[0]
@ -63,6 +64,7 @@ class ContractUpgradeFlowTest {
@After
fun tearDown() {
mockNet.stopNodes()
unsetCordappPackages()
}
@Test

View File

@ -12,6 +12,8 @@ import net.corda.testing.chooseIdentity
import net.corda.testing.getDefaultNotary
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockServices
import net.corda.testing.setCordappPackages
import net.corda.testing.unsetCordappPackages
import org.junit.After
import org.junit.Before
import org.junit.Test
@ -27,6 +29,7 @@ class FinalityFlowTests {
@Before
fun setup() {
setCordappPackages("net.corda.finance.contracts.asset")
mockNet = MockNetwork()
val nodes = mockNet.createSomeNodes(2)
nodeA = nodes.partyNodes[0]
@ -39,6 +42,7 @@ class FinalityFlowTests {
@After
fun tearDown() {
mockNet.stopNodes()
unsetCordappPackages()
}
@Test

View File

@ -8,11 +8,7 @@ import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.sequence
import net.corda.node.internal.StartedNode
import net.corda.testing.DUMMY_NOTARY_KEY
import net.corda.testing.MEGA_CORP
import net.corda.testing.MEGA_CORP_KEY
import net.corda.testing.MINI_CORP
import net.corda.testing.chooseIdentity
import net.corda.testing.*
import net.corda.testing.contracts.DummyContract
import net.corda.testing.getDefaultNotary
import net.corda.testing.node.MockNetwork
@ -34,11 +30,14 @@ class ResolveTransactionsFlowTest {
lateinit var a: StartedNode<MockNetwork.MockNode>
lateinit var b: StartedNode<MockNetwork.MockNode>
lateinit var notary: Party
val megaCorpServices = MockServices(MEGA_CORP_KEY)
val notaryServices = MockServices(DUMMY_NOTARY_KEY)
lateinit var megaCorpServices: MockServices
lateinit var notaryServices: MockServices
@Before
fun setup() {
setCordappPackages("net.corda.testing.contracts")
megaCorpServices = MockServices(MEGA_CORP_KEY)
notaryServices = MockServices(DUMMY_NOTARY_KEY)
mockNet = MockNetwork()
val nodes = mockNet.createSomeNodes()
a = nodes.partyNodes[0]
@ -52,6 +51,7 @@ class ResolveTransactionsFlowTest {
@After
fun tearDown() {
mockNet.stopNodes()
unsetCordappPackages()
}
// DOCSTART 1

View File

@ -16,7 +16,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class TransactionSerializationTests : TestDependencyInjectionBase() {
val TEST_CASH_PROGRAM_ID = "net.corda.core.serialization.TransactionSerializationTests.TestCash"
val TEST_CASH_PROGRAM_ID = "net.corda.core.serialization.TransactionSerializationTests\$TestCash"
class TestCash : Contract {
override fun verify(tx: LedgerTransaction) {
@ -45,8 +45,8 @@ class TransactionSerializationTests : TestDependencyInjectionBase() {
val outputState = TransactionState(TestCash.State(depositRef, 600.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY)
val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY)
val megaCorpServices = MockServices(MEGA_CORP_KEY)
val notaryServices = MockServices(DUMMY_NOTARY_KEY)
val megaCorpServices = MockServices(listOf("net.corda.core.serialization"), MEGA_CORP_KEY)
val notaryServices = MockServices(listOf("net.corda.core.serialization"), DUMMY_NOTARY_KEY)
lateinit var tx: TransactionBuilder
@Before

View File

@ -23,7 +23,8 @@ class IntegrationTestingTutorial {
@Test
fun `alice bob cash exchange example`() {
// START 1
driver(extraCordappPackagesToScan = listOf("net.corda.finance")) {
driver(startNodesInProcess = true,
extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset")) {
val aliceUser = User("aliceUser", "testPassword1", permissions = setOf(
startFlowPermission<CashIssueFlow>(),
startFlowPermission<CashPaymentFlow>()

View File

@ -12,10 +12,7 @@ import net.corda.finance.schemas.CashSchemaV1
import net.corda.nodeapi.internal.ServiceInfo
import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.transactions.ValidatingNotaryService
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.DUMMY_NOTARY_KEY
import net.corda.testing.chooseIdentity
import net.corda.testing.getDefaultNotary
import net.corda.testing.*
import net.corda.testing.node.MockNetwork
import org.junit.After
import org.junit.Assert
@ -33,6 +30,7 @@ class CustomVaultQueryTest {
@Before
fun setup() {
setCordappPackages("net.corda.finance.contracts.asset")
mockNet = MockNetwork(threadPerNode = true)
val notaryService = ServiceInfo(ValidatingNotaryService.type)
notaryNode = mockNet.createNode(
@ -52,6 +50,7 @@ class CustomVaultQueryTest {
@After
fun cleanUp() {
mockNet.stopNodes()
unsetCordappPackages()
}
@Test
@ -105,4 +104,4 @@ class CustomVaultQueryTest {
return Pair(balancesNodesA, balancesNodesB)
}
}
}

View File

@ -12,10 +12,7 @@ import net.corda.node.internal.StartedNode
import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.transactions.ValidatingNotaryService
import net.corda.nodeapi.internal.ServiceInfo
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.DUMMY_NOTARY_KEY
import net.corda.testing.chooseIdentity
import net.corda.testing.getDefaultNotary
import net.corda.testing.*
import net.corda.testing.node.MockNetwork
import org.junit.After
import org.junit.Before
@ -31,6 +28,7 @@ class FxTransactionBuildTutorialTest {
@Before
fun setup() {
setCordappPackages("net.corda.finance.contracts.asset")
mockNet = MockNetwork(threadPerNode = true)
val notaryService = ServiceInfo(ValidatingNotaryService.type)
notaryNode = mockNet.createNode(
@ -48,6 +46,7 @@ class FxTransactionBuildTutorialTest {
@After
fun cleanUp() {
mockNet.stopNodes()
unsetCordappPackages()
}
@Test

View File

@ -12,9 +12,7 @@ import net.corda.node.internal.StartedNode
import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.transactions.ValidatingNotaryService
import net.corda.nodeapi.internal.ServiceInfo
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.DUMMY_NOTARY_KEY
import net.corda.testing.chooseIdentity
import net.corda.testing.*
import net.corda.testing.node.MockNetwork
import org.junit.After
import org.junit.Before
@ -35,6 +33,7 @@ class WorkflowTransactionBuildTutorialTest {
@Before
fun setup() {
setCordappPackages("net.corda.docs")
mockNet = MockNetwork(threadPerNode = true)
val notaryService = ServiceInfo(ValidatingNotaryService.type)
notaryNode = mockNet.createNode(
@ -49,6 +48,7 @@ class WorkflowTransactionBuildTutorialTest {
@After
fun cleanUp() {
mockNet.stopNodes()
unsetCordappPackages()
}
@Test
@ -101,4 +101,4 @@ class WorkflowTransactionBuildTutorialTest {
assertEquals(completedRef, finalFromA)
assertEquals(completedRef, finalFromB)
}
}
}

View File

@ -219,7 +219,7 @@ a constructor with a single parameter of type ``ServiceHub``.
:end-before: DOCEND 2
These two flows leverage the oracle to provide the querying and signing operations. They get reference to the oracle,
which will have already been initialised by the node, using ``ServiceHub.cordappService``. Both flows are annotated with
which will have already been initialised by the node, using ``ServiceHub.cordappProvider``. Both flows are annotated with
``@InitiatedBy``. This tells the node which initiating flow (which are discussed in the next section) they are meant to
be executed with.

View File

@ -5,7 +5,11 @@ import net.corda.finance.contracts.FixOf
import net.corda.finance.contracts.Frequency
import net.corda.finance.contracts.Tenor
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.setCordappPackages
import net.corda.testing.transaction
import net.corda.testing.unsetCordappPackages
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import java.time.Instant
@ -163,6 +167,16 @@ class Cap {
}
}
@Before
fun setup() {
setCordappPackages("net.corda.finance.contracts.universal")
}
@After
fun tearDown() {
unsetCordappPackages()
}
@Test
fun issue() {
transaction {

View File

@ -3,7 +3,11 @@ package net.corda.finance.contracts.universal
import net.corda.finance.contracts.FixOf
import net.corda.finance.contracts.Tenor
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.setCordappPackages
import net.corda.testing.transaction
import net.corda.testing.unsetCordappPackages
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import java.time.Instant
@ -50,6 +54,16 @@ class Caplet {
val stateFinal = UniversalContract.State(listOf(DUMMY_NOTARY), contractFinal)
@Before
fun setup() {
setCordappPackages("net.corda.finance.contracts.universal")
}
@After
fun tearDown() {
unsetCordappPackages()
}
@Test
fun issue() {
transaction {

View File

@ -1,7 +1,11 @@
package net.corda.finance.contracts.universal
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.setCordappPackages
import net.corda.testing.transaction
import net.corda.testing.unsetCordappPackages
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import java.time.Instant
@ -47,6 +51,16 @@ class FXFwdTimeOption
val outState1 = UniversalContract.State(listOf(DUMMY_NOTARY), outContract1)
val outState2 = UniversalContract.State(listOf(DUMMY_NOTARY), outContract2)
@Before
fun setup() {
setCordappPackages("net.corda.finance.contracts.universal")
}
@After
fun tearDown() {
unsetCordappPackages()
}
@Test
fun `issue - signature`() {
transaction {

View File

@ -1,7 +1,11 @@
package net.corda.finance.contracts.universal
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.setCordappPackages
import net.corda.testing.transaction
import net.corda.testing.unsetCordappPackages
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import java.time.Instant
@ -38,6 +42,16 @@ class FXSwap {
val inState = UniversalContract.State(listOf(DUMMY_NOTARY), contract)
@Before
fun setup() {
setCordappPackages("net.corda.finance.contracts.universal")
}
@After
fun tearDown() {
unsetCordappPackages()
}
@Test
fun `issue - signature`() {

View File

@ -4,7 +4,11 @@ import net.corda.finance.contracts.FixOf
import net.corda.finance.contracts.Frequency
import net.corda.finance.contracts.Tenor
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.setCordappPackages
import net.corda.testing.transaction
import net.corda.testing.unsetCordappPackages
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import java.time.Instant
@ -129,6 +133,15 @@ class IRS {
val statePaymentFirst = UniversalContract.State(listOf(DUMMY_NOTARY), paymentFirst)
@Before
fun setup() {
setCordappPackages("net.corda.finance.contracts.universal")
}
@After
fun tearDown() {
unsetCordappPackages()
}
@Test
fun issue() {

View File

@ -2,7 +2,11 @@ package net.corda.finance.contracts.universal
import net.corda.finance.contracts.Frequency
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.setCordappPackages
import net.corda.testing.transaction
import net.corda.testing.unsetCordappPackages
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.time.Instant
import kotlin.test.assertEquals
@ -118,6 +122,15 @@ class RollOutTests {
next()
}
@Before
fun setup() {
setCordappPackages("net.corda.finance.contracts.universal")
}
@After
fun tearDown() {
unsetCordappPackages()
}
@Test
fun `arrangement equality transfer`() {

View File

@ -3,7 +3,11 @@ package net.corda.finance.contracts.universal
import net.corda.finance.contracts.Frequency
import net.corda.finance.contracts.Tenor
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.setCordappPackages
import net.corda.testing.transaction
import net.corda.testing.unsetCordappPackages
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import java.time.Instant
@ -56,6 +60,16 @@ class Swaption {
val stateInitial = UniversalContract.State(listOf(DUMMY_NOTARY), contractInitial)
@Before
fun setup() {
setCordappPackages("net.corda.finance.contracts.universal")
}
@After
fun tearDown() {
unsetCordappPackages()
}
@Test
fun issue() {
transaction {

View File

@ -1,7 +1,11 @@
package net.corda.finance.contracts.universal
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.setCordappPackages
import net.corda.testing.transaction
import net.corda.testing.unsetCordappPackages
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.time.Instant
import kotlin.test.assertEquals
@ -40,6 +44,16 @@ class ZeroCouponBond {
val outStateMove = UniversalContract.State(listOf(DUMMY_NOTARY), contractMove)
@Before
fun setup() {
setCordappPackages("net.corda.finance.contracts.universal")
}
@After
fun tearDown() {
unsetCordappPackages()
}
@Test
fun basic() {
assertEquals(Zero(), Zero())

View File

@ -7,8 +7,9 @@ import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.nodeapi.DummyContractBackdoor
val ANOTHER_DUMMY_PROGRAM_ID = "net.corda.finance.contracts.isolated.AnotherDummyContract"
const val ANOTHER_DUMMY_PROGRAM_ID = "net.corda.finance.contracts.isolated.AnotherDummyContract"
@Suppress("UNUSED")
class AnotherDummyContract : Contract, DummyContractBackdoor {
val magicString = "helloworld"
@ -31,5 +32,4 @@ class AnotherDummyContract : Contract, DummyContractBackdoor {
}
override fun inspectState(state: ContractState): Int = (state as State).magicNumber
}

View File

@ -0,0 +1,34 @@
package net.corda.finance.contracts.isolated
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.*
import net.corda.core.identity.Party
/**
* Just sends a dummy state to the other side: used for testing whether attachments with code in them are being
* loaded or blocked.
*/
class IsolatedDummyFlow {
@InitiatingFlow
class Initiator(val toWhom: Party) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val tx = AnotherDummyContract().generateInitial(
serviceHub.myInfo.legalIdentities.first().ref(0),
1234,
serviceHub.networkMapCache.notaryIdentities.first()
)
val stx = serviceHub.signInitialTransaction(tx)
subFlow(SendTransactionFlow(initiateFlow(toWhom), stx))
}
}
@InitiatedBy(Initiator::class)
class Acceptor(val session: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val stx = subFlow(ReceiveTransactionFlow(session, checkSufficientSignatures = false))
stx.verify(serviceHub)
}
}
}

View File

@ -2,7 +2,4 @@ package net.corda.finance.contracts.isolated
import net.corda.core.node.CordaPluginRegistry
/**
* Dummy plugin for testing plugin loading
*/
class DummyPlugin : CordaPluginRegistry()
class IsolatedPlugin : CordaPluginRegistry()

View File

@ -1 +1 @@
net.corda.finance.contracts.isolated.DummyPlugin
net.corda.finance.contracts.isolated.IsolatedPlugin

View File

@ -40,6 +40,9 @@ import java.util.*
* which may need to be tracked. That, in turn, requires validation logic (there is a bean validator that knows how
* to do this in the Apache BVal project).
*/
val CP_PROGRAM_ID = "net.corda.finance.contracts.CommercialPaper"
// TODO: Generalise the notion of an owned instrument into a superclass/supercontract. Consider composition vs inheritance.
class CommercialPaper : Contract {
companion object {

View File

@ -173,7 +173,7 @@ object TwoPartyTradeFlow {
val partSignedTx = serviceHub.signInitialTransaction(ptx, cashSigningPubKeys)
// Sync up confidential identities in the transaction with our counterparty
subFlow(IdentitySyncFlow.Send(sellerSession, ptx.toWireTransaction()))
subFlow(IdentitySyncFlow.Send(sellerSession, ptx.toWireTransaction(serviceHub)))
// Send the signed transaction to the seller, who must then sign it themselves and commit
// it to the ledger by sending it to the notary.

View File

@ -24,37 +24,39 @@ public class CashTestsJava {
@Test
public void trivial() {
transaction(tx -> {
tx.attachment(CASH_PROGRAM_ID);
tx.input(CASH_PROGRAM_ID, inState);
tx.tweak(tw -> {
tw.output(CASH_PROGRAM_ID, new Cash.State(issuedBy(DOLLARS(2000), defaultIssuer), new AnonymousParty(getMINI_CORP_PUBKEY())));
tw.output(CASH_PROGRAM_ID, () -> new Cash.State(issuedBy(DOLLARS(2000), defaultIssuer), new AnonymousParty(getMINI_CORP_PUBKEY())));
tw.command(getMEGA_CORP_PUBKEY(), new Cash.Commands.Move());
return tw.failsWith("the amounts balance");
});
tx.tweak(tw -> {
tw.output(CASH_PROGRAM_ID, outState);
tw.output(CASH_PROGRAM_ID, () -> outState );
tw.command(getMEGA_CORP_PUBKEY(), DummyCommandData.INSTANCE);
// Invalid command
return tw.failsWith("required net.corda.finance.contracts.asset.Cash.Commands.Move command");
});
tx.tweak(tw -> {
tw.output(CASH_PROGRAM_ID, outState);
tw.output(CASH_PROGRAM_ID, () -> outState);
tw.command(getMINI_CORP_PUBKEY(), new Cash.Commands.Move());
return tw.failsWith("the owning keys are a subset of the signing keys");
});
tx.tweak(tw -> {
tw.output(CASH_PROGRAM_ID, outState);
tw.output(CASH_PROGRAM_ID, () -> outState);
// issuedBy() can't be directly imported because it conflicts with other identically named functions
// with different overloads (for some reason).
tw.output(CASH_PROGRAM_ID, outState.issuedBy(getMINI_CORP()));
tw.output(CASH_PROGRAM_ID, () -> outState.issuedBy(getMINI_CORP()));
tw.command(getMEGA_CORP_PUBKEY(), new Cash.Commands.Move());
return tw.failsWith("at least one cash input");
});
// Simple reallocation works.
return tx.tweak(tw -> {
tw.output(CASH_PROGRAM_ID, outState);
tw.output(CASH_PROGRAM_ID, () -> outState);
tw.command(getMEGA_CORP_PUBKEY(), new Cash.Commands.Move());
return tw.verifies();
});

View File

@ -16,6 +16,7 @@ import net.corda.testing.*
import net.corda.testing.contracts.fillWithSomeTestCash
import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@ -93,12 +94,14 @@ class CommercialPaperTestsGeneric {
val someProfits = 1200.DOLLARS `issued by` issuer
ledger {
unverifiedTransaction {
attachment(CASH_PROGRAM_ID)
output(CASH_PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE)
output(CASH_PROGRAM_ID, "some profits", someProfits.STATE `owned by` MEGA_CORP)
}
// Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") {
attachments(CP_PROGRAM_ID, JavaCommercialPaper.JCP_PROGRAM_ID)
output(thisTest.getContract(), "paper") { thisTest.getPaper() }
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) }
timeWindow(TEST_TX_TIME)
@ -108,6 +111,7 @@ class CommercialPaperTestsGeneric {
// The CP is sold to alice for her $900, $100 less than the face value. At 10% interest after only 7 days,
// that sounds a bit too good to be true!
transaction("Trade") {
attachments(CASH_PROGRAM_ID, JavaCommercialPaper.JCP_PROGRAM_ID)
input("paper")
input("alice's $900")
output(CASH_PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP }
@ -120,6 +124,7 @@ class CommercialPaperTestsGeneric {
// Time passes, and Alice redeem's her CP for $1000, netting a $100 profit. MegaCorp has received $1200
// as a single payment from somewhere and uses it to pay Alice off, keeping the remaining $200 as change.
transaction("Redemption") {
attachments(CP_PROGRAM_ID, JavaCommercialPaper.JCP_PROGRAM_ID)
input("alice's paper")
input("some profits")
@ -158,6 +163,8 @@ class CommercialPaperTestsGeneric {
@Test
fun `key mismatch at issue`() {
transaction {
attachment(CP_PROGRAM_ID)
attachment(JavaCommercialPaper.JCP_PROGRAM_ID)
output(thisTest.getContract()) { thisTest.getPaper() }
command(MINI_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) }
timeWindow(TEST_TX_TIME)
@ -168,6 +175,8 @@ class CommercialPaperTestsGeneric {
@Test
fun `face value is not zero`() {
transaction {
attachment(CP_PROGRAM_ID)
attachment(JavaCommercialPaper.JCP_PROGRAM_ID)
output(thisTest.getContract()) { thisTest.getPaper().withFaceValue(0.DOLLARS `issued by` issuer) }
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) }
timeWindow(TEST_TX_TIME)
@ -178,6 +187,8 @@ class CommercialPaperTestsGeneric {
@Test
fun `maturity date not in the past`() {
transaction {
attachment(CP_PROGRAM_ID)
attachment(JavaCommercialPaper.JCP_PROGRAM_ID)
output(thisTest.getContract()) { thisTest.getPaper().withMaturityDate(TEST_TX_TIME - 10.days) }
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) }
timeWindow(TEST_TX_TIME)
@ -188,6 +199,8 @@ class CommercialPaperTestsGeneric {
@Test
fun `issue cannot replace an existing state`() {
transaction {
attachment(CP_PROGRAM_ID)
attachment(JavaCommercialPaper.JCP_PROGRAM_ID)
input(thisTest.getContract(), thisTest.getPaper())
output(thisTest.getContract()) { thisTest.getPaper() }
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) }
@ -214,8 +227,10 @@ class CommercialPaperTestsGeneric {
private lateinit var moveTX: SignedTransaction
@Test
// @Test
@Ignore
fun `issue move and then redeem`() {
setCordappPackages("net.corda.finance.contracts")
initialiseTestSerialization()
val aliceDatabaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(ALICE_KEY))
val databaseAlice = aliceDatabaseAndServices.first

View File

@ -54,8 +54,8 @@ class CashTests : TestDependencyInjectionBase() {
@Before
fun setUp() {
LogHelper.setLevel(NodeVaultService::class)
megaCorpServices = MockServices(MEGA_CORP_KEY)
val databaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(MINI_CORP_KEY, MEGA_CORP_KEY, OUR_KEY))
megaCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), MEGA_CORP_KEY)
val databaseAndServices = makeTestDatabaseAndMockServices(cordappPackages = listOf("net.corda.finance.contracts.asset"), keys = listOf(MINI_CORP_KEY, MEGA_CORP_KEY, OUR_KEY))
database = databaseAndServices.first
miniCorpServices = databaseAndServices.second
@ -84,6 +84,7 @@ class CashTests : TestDependencyInjectionBase() {
@Test
fun trivial() {
transaction {
attachment(CASH_PROGRAM_ID)
input(CASH_PROGRAM_ID) { inState }
tweak {
@ -121,6 +122,7 @@ class CashTests : TestDependencyInjectionBase() {
fun `issue by move`() {
// Check we can't "move" money into existence.
transaction {
attachment(CASH_PROGRAM_ID)
input(CASH_PROGRAM_ID) { DummyState() }
output(CASH_PROGRAM_ID) { outState }
command(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
@ -134,11 +136,13 @@ class CashTests : TestDependencyInjectionBase() {
// Check we can issue money only as long as the issuer institution is a command signer, i.e. any recognised
// institution is allowed to issue as much cash as they want.
transaction {
attachment(CASH_PROGRAM_ID)
output(CASH_PROGRAM_ID) { outState }
command(ALICE_PUBKEY) { Cash.Commands.Issue() }
this `fails with` "output states are issued by a command signer"
}
transaction {
attachment(CASH_PROGRAM_ID)
output(CASH_PROGRAM_ID) {
Cash.State(
amount = 1000.DOLLARS `issued by` MINI_CORP.ref(12, 34),
@ -156,7 +160,7 @@ class CashTests : TestDependencyInjectionBase() {
// Test generation works.
val tx: WireTransaction = TransactionBuilder(notary = null).apply {
Cash().generateIssue(this, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = AnonymousParty(ALICE_PUBKEY), notary = DUMMY_NOTARY)
}.toWireTransaction()
}.toWireTransaction(miniCorpServices)
assertTrue(tx.inputs.isEmpty())
val s = tx.outputsOfType<Cash.State>().single()
assertEquals(100.DOLLARS `issued by` MINI_CORP.ref(12, 34), s.amount)
@ -173,7 +177,7 @@ class CashTests : TestDependencyInjectionBase() {
val amount = 100.DOLLARS `issued by` MINI_CORP.ref(12, 34)
val tx: WireTransaction = TransactionBuilder(notary = null).apply {
Cash().generateIssue(this, amount, owner = AnonymousParty(ALICE_PUBKEY), notary = DUMMY_NOTARY)
}.toWireTransaction()
}.toWireTransaction(miniCorpServices)
assertTrue(tx.inputs.isEmpty())
assertEquals(tx.outputs[0], tx.outputs[0])
}
@ -182,6 +186,7 @@ class CashTests : TestDependencyInjectionBase() {
fun `extended issue examples`() {
// We can consume $1000 in a transaction and output $2000 as long as it's signed by an issuer.
transaction {
attachment(CASH_PROGRAM_ID)
input(CASH_PROGRAM_ID) { issuerInState }
output(CASH_PROGRAM_ID) { inState.copy(amount = inState.amount * 2) }
@ -200,6 +205,7 @@ class CashTests : TestDependencyInjectionBase() {
// Can't use an issue command to lower the amount.
transaction {
attachment(CASH_PROGRAM_ID)
input(CASH_PROGRAM_ID) { inState }
output(CASH_PROGRAM_ID) { inState.copy(amount = inState.amount.splitEvenly(2).first()) }
command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
@ -208,6 +214,7 @@ class CashTests : TestDependencyInjectionBase() {
// Can't have an issue command that doesn't actually issue money.
transaction {
attachment(CASH_PROGRAM_ID)
input(CASH_PROGRAM_ID) { inState }
output(CASH_PROGRAM_ID) { inState }
command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
@ -216,6 +223,7 @@ class CashTests : TestDependencyInjectionBase() {
// Can't have any other commands if we have an issue command (because the issue command overrules them)
transaction {
attachment(CASH_PROGRAM_ID)
input(CASH_PROGRAM_ID) { inState }
output(CASH_PROGRAM_ID) { inState.copy(amount = inState.amount * 2) }
command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
@ -250,6 +258,7 @@ class CashTests : TestDependencyInjectionBase() {
fun testMergeSplit() {
// Splitting value works.
transaction {
attachment(CASH_PROGRAM_ID)
command(ALICE_PUBKEY) { Cash.Commands.Move() }
tweak {
input(CASH_PROGRAM_ID) { inState }
@ -278,12 +287,14 @@ class CashTests : TestDependencyInjectionBase() {
@Test
fun zeroSizedValues() {
transaction {
attachment(CASH_PROGRAM_ID)
input(CASH_PROGRAM_ID) { inState }
input(CASH_PROGRAM_ID) { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) }
command(ALICE_PUBKEY) { Cash.Commands.Move() }
this `fails with` "zero sized inputs"
}
transaction {
attachment(CASH_PROGRAM_ID)
input(CASH_PROGRAM_ID) { inState }
output(CASH_PROGRAM_ID) { inState }
output(CASH_PROGRAM_ID) { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) }
@ -296,6 +307,7 @@ class CashTests : TestDependencyInjectionBase() {
fun trivialMismatches() {
// Can't change issuer.
transaction {
attachment(CASH_PROGRAM_ID)
input(CASH_PROGRAM_ID) { inState }
output(CASH_PROGRAM_ID) { outState `issued by` MINI_CORP }
command(ALICE_PUBKEY) { Cash.Commands.Move() }
@ -303,6 +315,7 @@ class CashTests : TestDependencyInjectionBase() {
}
// Can't change deposit reference when splitting.
transaction {
attachment(CASH_PROGRAM_ID)
val splits2 = inState.amount.splitEvenly(2)
input(CASH_PROGRAM_ID) { inState }
for (i in 0..1) output(CASH_PROGRAM_ID) { outState.copy(amount = splits2[i]).editDepositRef(i.toByte()) }
@ -311,6 +324,7 @@ class CashTests : TestDependencyInjectionBase() {
}
// Can't mix currencies.
transaction {
attachment(CASH_PROGRAM_ID)
input(CASH_PROGRAM_ID) { inState }
output(CASH_PROGRAM_ID) { outState.copy(amount = 800.DOLLARS `issued by` defaultIssuer) }
output(CASH_PROGRAM_ID) { outState.copy(amount = 200.POUNDS `issued by` defaultIssuer) }
@ -318,6 +332,7 @@ class CashTests : TestDependencyInjectionBase() {
this `fails with` "the amounts balance"
}
transaction {
attachment(CASH_PROGRAM_ID)
input(CASH_PROGRAM_ID) { inState }
input(CASH_PROGRAM_ID) {
inState.copy(
@ -331,6 +346,7 @@ class CashTests : TestDependencyInjectionBase() {
}
// Can't have superfluous input states from different issuers.
transaction {
attachment(CASH_PROGRAM_ID)
input(CASH_PROGRAM_ID) { inState }
input(CASH_PROGRAM_ID) { inState `issued by` MINI_CORP }
output(CASH_PROGRAM_ID) { outState }
@ -339,6 +355,7 @@ class CashTests : TestDependencyInjectionBase() {
}
// Can't combine two different deposits at the same issuer.
transaction {
attachment(CASH_PROGRAM_ID)
input(CASH_PROGRAM_ID) { inState }
input(CASH_PROGRAM_ID) { inState.editDepositRef(3) }
output(CASH_PROGRAM_ID) { outState.copy(amount = inState.amount * 2).editDepositRef(3) }
@ -351,6 +368,7 @@ class CashTests : TestDependencyInjectionBase() {
fun exitLedger() {
// Single input/output straightforward case.
transaction {
attachment(CASH_PROGRAM_ID)
input(CASH_PROGRAM_ID) { issuerInState }
output(CASH_PROGRAM_ID) { issuerInState.copy(amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)) }
@ -376,6 +394,7 @@ class CashTests : TestDependencyInjectionBase() {
fun `exit ledger with multiple issuers`() {
// Multi-issuer case.
transaction {
attachment(CASH_PROGRAM_ID)
input(CASH_PROGRAM_ID) { issuerInState }
input(CASH_PROGRAM_ID) { issuerInState.copy(owner = MINI_CORP) `issued by` MINI_CORP }
@ -398,6 +417,7 @@ class CashTests : TestDependencyInjectionBase() {
fun `exit cash not held by its issuer`() {
// Single input/output straightforward case.
transaction {
attachment(CASH_PROGRAM_ID)
input(CASH_PROGRAM_ID) { inState }
output(CASH_PROGRAM_ID) { outState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) }
command(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) }
@ -409,6 +429,7 @@ class CashTests : TestDependencyInjectionBase() {
@Test
fun multiIssuer() {
transaction {
attachment(CASH_PROGRAM_ID)
// Gather 2000 dollars from two different issuers.
input(CASH_PROGRAM_ID) { inState }
input(CASH_PROGRAM_ID) { inState `issued by` MINI_CORP }
@ -437,6 +458,7 @@ class CashTests : TestDependencyInjectionBase() {
fun multiCurrency() {
// Check we can do an atomic currency trade tx.
transaction {
attachment(CASH_PROGRAM_ID)
val pounds = Cash.State(658.POUNDS `issued by` MINI_CORP.ref(3, 4, 5), AnonymousParty(BOB_PUBKEY))
input(CASH_PROGRAM_ID) { inState `owned by` AnonymousParty(ALICE_PUBKEY) }
input(CASH_PROGRAM_ID) { pounds }
@ -477,7 +499,7 @@ class CashTests : TestDependencyInjectionBase() {
private fun makeExit(amount: Amount<Currency>, corp: Party, depositRef: Byte = 1): WireTransaction {
val tx = TransactionBuilder(DUMMY_NOTARY)
Cash().generateExit(tx, Amount(amount.quantity, Issued(corp.ref(depositRef), amount.token)), WALLET)
return tx.toWireTransaction()
return tx.toWireTransaction(miniCorpServices)
}
private fun makeSpend(amount: Amount<Currency>, dest: AbstractParty): WireTransaction {
@ -485,7 +507,7 @@ class CashTests : TestDependencyInjectionBase() {
database.transaction {
Cash.generateSpend(miniCorpServices, tx, amount, dest)
}
return tx.toWireTransaction()
return tx.toWireTransaction(miniCorpServices)
}
/**
@ -761,8 +783,11 @@ class CashTests : TestDependencyInjectionBase() {
// Double spend.
@Test
fun chainCashDoubleSpendFailsWith() {
ledger {
val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), MEGA_CORP_KEY)
ledger(mockService) {
unverifiedTransaction {
attachment(CASH_PROGRAM_ID)
output(CASH_PROGRAM_ID, "MEGA_CORP cash") {
Cash.State(
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
@ -772,17 +797,19 @@ class CashTests : TestDependencyInjectionBase() {
}
transaction {
attachment(CASH_PROGRAM_ID)
input("MEGA_CORP cash")
output(CASH_PROGRAM_ID, "MEGA_CORP cash".output<Cash.State>().copy(owner = AnonymousParty(ALICE_PUBKEY)))
output(CASH_PROGRAM_ID, "MEGA_CORP cash 2", "MEGA_CORP cash".output<Cash.State>().copy(owner = AnonymousParty(ALICE_PUBKEY)) )
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
this.verifies()
}
tweak {
transaction {
attachment(CASH_PROGRAM_ID)
input("MEGA_CORP cash")
// We send it to another pubkey so that the transaction is not identical to the previous one
output(CASH_PROGRAM_ID, "MEGA_CORP cash".output<Cash.State>().copy(owner = ALICE))
output(CASH_PROGRAM_ID, "MEGA_CORP cash 3", "MEGA_CORP cash".output<Cash.State>().copy(owner = ALICE))
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
this.verifies()
}
@ -804,7 +831,7 @@ class CashTests : TestDependencyInjectionBase() {
)
Cash.generateSpend(miniCorpServices, tx, payments)
}
val wtx = tx.toWireTransaction()
val wtx = tx.toWireTransaction(miniCorpServices)
fun out(i: Int) = wtx.getOutput(i) as Cash.State
assertEquals(4, wtx.outputs.size)
assertEquals(80.DOLLARS, out(0).amount.withoutIssuer())

View File

@ -48,13 +48,15 @@ class ObligationTests {
beneficiary = CHARLIE
)
private val outState = inState.copy(beneficiary = AnonymousParty(BOB_PUBKEY))
private val miniCorpServices = MockServices(MINI_CORP_KEY)
private val miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), MINI_CORP_KEY)
private val notaryServices = MockServices(DUMMY_NOTARY_KEY)
private val mockService = MockServices(listOf("net.corda.finance.contracts.asset"))
private fun cashObligationTestRoots(
group: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
) = group.apply {
unverifiedTransaction {
attachments(OBLIGATION_PROGRAM_ID)
output(OBLIGATION_PROGRAM_ID, "Alice's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION between Pair(ALICE, BOB))
output(OBLIGATION_PROGRAM_ID, "Bob's $1,000,000 obligation to Alice", oneMillionDollars.OBLIGATION between Pair(BOB, ALICE))
output(OBLIGATION_PROGRAM_ID, "MegaCorp's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, BOB))
@ -70,6 +72,7 @@ class ObligationTests {
@Test
fun trivial() {
transaction {
attachments(OBLIGATION_PROGRAM_ID)
input(OBLIGATION_PROGRAM_ID) { inState }
tweak {
@ -107,6 +110,7 @@ class ObligationTests {
fun `issue debt`() {
// Check we can't "move" debt into existence.
transaction {
attachments(DUMMY_PROGRAM_ID, OBLIGATION_PROGRAM_ID)
input(DUMMY_PROGRAM_ID) { DummyState() }
output(OBLIGATION_PROGRAM_ID) { outState }
command(MINI_CORP_PUBKEY) { Obligation.Commands.Move() }
@ -117,11 +121,13 @@ class ObligationTests {
// Check we can issue money only as long as the issuer institution is a command signer, i.e. any recognised
// institution is allowed to issue as much cash as they want.
transaction {
attachments(OBLIGATION_PROGRAM_ID)
output(OBLIGATION_PROGRAM_ID) { outState }
command(CHARLIE.owningKey) { Obligation.Commands.Issue() }
this `fails with` "output states are issued by a command signer"
}
transaction {
attachments(OBLIGATION_PROGRAM_ID)
output(OBLIGATION_PROGRAM_ID) {
Obligation.State(
obligor = MINI_CORP,
@ -139,7 +145,7 @@ class ObligationTests {
val tx = TransactionBuilder(notary = null).apply {
Obligation<Currency>().generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
beneficiary = CHARLIE, notary = DUMMY_NOTARY)
}.toWireTransaction()
}.toWireTransaction(miniCorpServices)
assertTrue(tx.inputs.isEmpty())
val expected = Obligation.State(
obligor = MINI_CORP,
@ -154,6 +160,7 @@ class ObligationTests {
// We can consume $1000 in a transaction and output $2000 as long as it's signed by an issuer.
transaction {
attachments(OBLIGATION_PROGRAM_ID)
input(OBLIGATION_PROGRAM_ID) { inState }
output(OBLIGATION_PROGRAM_ID) { inState.copy(quantity = inState.amount.quantity * 2) }
@ -172,6 +179,7 @@ class ObligationTests {
// Can't use an issue command to lower the amount.
transaction {
attachments(OBLIGATION_PROGRAM_ID)
input(OBLIGATION_PROGRAM_ID) { inState }
output(OBLIGATION_PROGRAM_ID) { inState.copy(quantity = inState.amount.quantity / 2) }
command(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue() }
@ -180,6 +188,7 @@ class ObligationTests {
// Can't have an issue command that doesn't actually issue money.
transaction {
attachments(OBLIGATION_PROGRAM_ID)
input(OBLIGATION_PROGRAM_ID) { inState }
output(OBLIGATION_PROGRAM_ID) { inState }
command(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue() }
@ -188,6 +197,7 @@ class ObligationTests {
// Can't have any other commands if we have an issue command (because the issue command overrules them).
transaction {
attachments(OBLIGATION_PROGRAM_ID)
input(OBLIGATION_PROGRAM_ID) { inState }
output(OBLIGATION_PROGRAM_ID) { inState.copy(quantity = inState.amount.quantity * 2) }
command(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue() }
@ -210,7 +220,7 @@ class ObligationTests {
val tx = TransactionBuilder(DUMMY_NOTARY).apply {
Obligation<Currency>().generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
beneficiary = MINI_CORP, notary = DUMMY_NOTARY)
}.toWireTransaction()
}.toWireTransaction(miniCorpServices)
// Include the previously issued obligation in a new issuance command
@ -228,7 +238,7 @@ class ObligationTests {
val obligationBobToAlice = getStateAndRef(oneMillionDollars.OBLIGATION between Pair(BOB, ALICE), OBLIGATION_PROGRAM_ID)
val tx = TransactionBuilder(DUMMY_NOTARY).apply {
Obligation<Currency>().generateCloseOutNetting(this, ALICE, obligationAliceToBob, obligationBobToAlice)
}.toWireTransaction()
}.toWireTransaction(miniCorpServices)
assertEquals(0, tx.outputs.size)
}
@ -240,7 +250,7 @@ class ObligationTests {
val obligationBobToAlice = getStateAndRef(oneMillionDollars.OBLIGATION between Pair(BOB, ALICE), OBLIGATION_PROGRAM_ID)
val tx = TransactionBuilder(DUMMY_NOTARY).apply {
Obligation<Currency>().generateCloseOutNetting(this, ALICE, obligationAliceToBob, obligationBobToAlice)
}.toWireTransaction()
}.toWireTransaction(miniCorpServices)
assertEquals(1, tx.outputs.size)
val actual = tx.getOutput(0)
@ -255,7 +265,7 @@ class ObligationTests {
val obligationBobToAlice = getStateAndRef(oneMillionDollars.OBLIGATION between Pair(BOB, ALICE), OBLIGATION_PROGRAM_ID)
val tx = TransactionBuilder(DUMMY_NOTARY).apply {
Obligation<Currency>().generatePaymentNetting(this, obligationAliceToBob.state.data.amount.token, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
}.toWireTransaction()
}.toWireTransaction(miniCorpServices)
assertEquals(0, tx.outputs.size)
}
@ -269,7 +279,7 @@ class ObligationTests {
val obligationBobToAliceState = obligationBobToAlice.state.data
val tx = TransactionBuilder(DUMMY_NOTARY).apply {
Obligation<Currency>().generatePaymentNetting(this, obligationAliceToBobState.amount.token, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
}.toWireTransaction()
}.toWireTransaction(miniCorpServices)
assertEquals(1, tx.outputs.size)
val expected = obligationBobToAliceState.copy(quantity = obligationBobToAliceState.quantity - obligationAliceToBobState.quantity)
val actual = tx.getOutput(0)
@ -327,18 +337,18 @@ class ObligationTests {
initialiseTestSerialization()
val cashTx = TransactionBuilder(null).apply {
Cash().generateIssue(this, 100.DOLLARS `issued by` defaultIssuer, MINI_CORP, DUMMY_NOTARY)
}.toWireTransaction()
}.toWireTransaction(miniCorpServices)
// Generate a transaction issuing the obligation
val obligationTx = TransactionBuilder(null).apply {
Obligation<Currency>().generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
beneficiary = MINI_CORP, notary = DUMMY_NOTARY)
}.toWireTransaction()
}.toWireTransaction(miniCorpServices)
// Now generate a transaction settling the obligation
val settleTx = TransactionBuilder(DUMMY_NOTARY).apply {
Obligation<Currency>().generateSettle(this, listOf(obligationTx.outRef(0)), listOf(cashTx.outRef(0)), Cash.Commands.Move(), DUMMY_NOTARY)
}.toWireTransaction()
}.toWireTransaction(miniCorpServices)
assertEquals(2, settleTx.inputs.size)
assertEquals(1, settleTx.outputs.size)
}
@ -346,9 +356,10 @@ class ObligationTests {
@Test
fun `close-out netting`() {
// Try netting out two obligations
ledger {
ledger(mockService) {
cashObligationTestRoots(this)
transaction("Issuance") {
attachments(OBLIGATION_PROGRAM_ID)
input("Alice's $1,000,000 obligation to Bob")
input("Bob's $1,000,000 obligation to Alice")
// Note we can sign with either key here
@ -361,9 +372,10 @@ class ObligationTests {
// Try netting out two obligations, with the third uninvolved obligation left
// as-is
ledger {
ledger(mockService) {
cashObligationTestRoots(this)
transaction("Issuance") {
attachments(OBLIGATION_PROGRAM_ID)
input("Alice's $1,000,000 obligation to Bob")
input("Bob's $1,000,000 obligation to Alice")
input("MegaCorp's $1,000,000 obligation to Bob")
@ -379,6 +391,7 @@ class ObligationTests {
ledger {
cashObligationTestRoots(this)
transaction("Issuance") {
attachments(OBLIGATION_PROGRAM_ID)
input("Alice's $1,000,000 obligation to Bob")
input("Bob's $1,000,000 obligation to Alice")
output(OBLIGATION_PROGRAM_ID, "change") { (oneMillionDollars.splitEvenly(2).first()).OBLIGATION between Pair(ALICE, BOB) }
@ -392,6 +405,7 @@ class ObligationTests {
ledger {
cashObligationTestRoots(this)
transaction("Issuance") {
attachments(OBLIGATION_PROGRAM_ID)
input("Alice's $1,000,000 obligation to Bob")
input("Bob's $1,000,000 obligation to Alice")
command(MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) }
@ -404,9 +418,10 @@ class ObligationTests {
@Test
fun `payment netting`() {
// Try netting out two obligations
ledger {
ledger(mockService) {
cashObligationTestRoots(this)
transaction("Issuance") {
attachments(OBLIGATION_PROGRAM_ID)
input("Alice's $1,000,000 obligation to Bob")
input("Bob's $1,000,000 obligation to Alice")
command(ALICE_PUBKEY, BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
@ -421,6 +436,7 @@ class ObligationTests {
ledger {
cashObligationTestRoots(this)
transaction("Issuance") {
attachments(OBLIGATION_PROGRAM_ID)
input("Alice's $1,000,000 obligation to Bob")
input("Bob's $1,000,000 obligation to Alice")
command(BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
@ -430,9 +446,10 @@ class ObligationTests {
}
// Multilateral netting, A -> B -> C which can net down to A -> C
ledger {
ledger(mockService) {
cashObligationTestRoots(this)
transaction("Issuance") {
attachments(OBLIGATION_PROGRAM_ID)
input("Bob's $1,000,000 obligation to Alice")
input("MegaCorp's $1,000,000 obligation to Bob")
output(OBLIGATION_PROGRAM_ID, "MegaCorp's $1,000,000 obligation to Alice") { oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, ALICE) }
@ -444,12 +461,13 @@ class ObligationTests {
}
// Multilateral netting without the key of the receiving party
ledger {
ledger(mockService) {
cashObligationTestRoots(this)
transaction("Issuance") {
attachments(OBLIGATION_PROGRAM_ID)
input("Bob's $1,000,000 obligation to Alice")
input("MegaCorp's $1,000,000 obligation to Bob")
output("MegaCorp's $1,000,000 obligation to Alice") { oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, ALICE) }
output(OBLIGATION_PROGRAM_ID, "MegaCorp's $1,000,000 obligation to Alice") { oneMillionDollars.OBLIGATION between Pair(MEGA_CORP, ALICE) }
command(ALICE_PUBKEY, BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) }
timeWindow(TEST_TX_TIME)
this `fails with` "all involved parties have signed"
@ -463,6 +481,7 @@ class ObligationTests {
ledger {
cashObligationTestRoots(this)
transaction("Settlement") {
attachments(OBLIGATION_PROGRAM_ID)
input("Alice's $1,000,000 obligation to Bob")
input("Alice's $1,000,000")
output(OBLIGATION_PROGRAM_ID, "Bob's $1,000,000") { 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` BOB }
@ -477,6 +496,7 @@ class ObligationTests {
val halfAMillionDollars = 500000.DOLLARS `issued by` defaultIssuer
ledger {
transaction("Settlement") {
attachments(OBLIGATION_PROGRAM_ID, CASH_PROGRAM_ID)
input(OBLIGATION_PROGRAM_ID, oneMillionDollars.OBLIGATION between Pair(ALICE, BOB))
input(CASH_PROGRAM_ID, 500000.DOLLARS.CASH `issued by` defaultIssuer `owned by` ALICE)
output(OBLIGATION_PROGRAM_ID, "Alice's $500,000 obligation to Bob") { halfAMillionDollars.OBLIGATION between Pair(ALICE, BOB) }
@ -492,6 +512,7 @@ class ObligationTests {
val defaultedObligation: Obligation.State<Currency> = (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB)).copy(lifecycle = Lifecycle.DEFAULTED)
ledger {
transaction("Settlement") {
attachments(OBLIGATION_PROGRAM_ID, CASH_PROGRAM_ID)
input(OBLIGATION_PROGRAM_ID, defaultedObligation) // Alice's defaulted $1,000,000 obligation to Bob
input(CASH_PROGRAM_ID, 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` ALICE)
output(OBLIGATION_PROGRAM_ID, "Bob's $1,000,000") { 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` BOB }
@ -505,6 +526,7 @@ class ObligationTests {
ledger {
cashObligationTestRoots(this)
transaction("Settlement") {
attachments(OBLIGATION_PROGRAM_ID)
input("Alice's $1,000,000 obligation to Bob")
input("Alice's $1,000,000")
output(OBLIGATION_PROGRAM_ID, "Bob's $1,000,000") { 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` BOB }
@ -527,10 +549,12 @@ class ObligationTests {
// Try settling a simple commodity obligation
ledger {
unverifiedTransaction {
attachments(OBLIGATION_PROGRAM_ID)
output(OBLIGATION_PROGRAM_ID, "Alice's 1 FCOJ obligation to Bob", oneUnitFcojObligation between Pair(ALICE, BOB))
output(OBLIGATION_PROGRAM_ID, "Alice's 1 FCOJ", CommodityContract.State(oneUnitFcoj, ALICE))
}
transaction("Settlement") {
attachments(OBLIGATION_PROGRAM_ID)
input("Alice's 1 FCOJ obligation to Bob")
input("Alice's 1 FCOJ")
output(OBLIGATION_PROGRAM_ID, "Bob's 1 FCOJ") { CommodityContract.State(oneUnitFcoj, BOB) }
@ -548,6 +572,7 @@ class ObligationTests {
ledger {
cashObligationTestRoots(this)
transaction("Settlement") {
attachments(OBLIGATION_PROGRAM_ID)
input("Alice's $1,000,000 obligation to Bob")
output(OBLIGATION_PROGRAM_ID, "Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB)).copy(lifecycle = Lifecycle.DEFAULTED) }
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Lifecycle.DEFAULTED) }
@ -559,6 +584,7 @@ class ObligationTests {
val pastTestTime = TEST_TX_TIME - 7.days
val futureTestTime = TEST_TX_TIME + 7.days
transaction("Settlement") {
attachments(OBLIGATION_PROGRAM_ID)
input(OBLIGATION_PROGRAM_ID, oneMillionDollars.OBLIGATION between Pair(ALICE, BOB) `at` futureTestTime)
output(OBLIGATION_PROGRAM_ID, "Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB) `at` futureTestTime).copy(lifecycle = Lifecycle.DEFAULTED) }
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Lifecycle.DEFAULTED) }
@ -567,8 +593,10 @@ class ObligationTests {
}
// Try defaulting an obligation that is now in the past
unsetCordappPackages()
ledger {
transaction("Settlement") {
attachments(OBLIGATION_PROGRAM_ID)
input(OBLIGATION_PROGRAM_ID, oneMillionDollars.OBLIGATION between Pair(ALICE, BOB) `at` pastTestTime)
output(OBLIGATION_PROGRAM_ID, "Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB) `at` pastTestTime).copy(lifecycle = Lifecycle.DEFAULTED) }
command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Lifecycle.DEFAULTED) }
@ -583,6 +611,7 @@ class ObligationTests {
fun testMergeSplit() {
// Splitting value works.
transaction {
attachments(OBLIGATION_PROGRAM_ID)
command(CHARLIE.owningKey) { Obligation.Commands.Move() }
tweak {
input(OBLIGATION_PROGRAM_ID) { inState }
@ -609,6 +638,7 @@ class ObligationTests {
@Test
fun zeroSizedValues() {
transaction {
attachments(OBLIGATION_PROGRAM_ID)
command(CHARLIE.owningKey) { Obligation.Commands.Move() }
tweak {
input(OBLIGATION_PROGRAM_ID) { inState }
@ -630,6 +660,7 @@ class ObligationTests {
fun trivialMismatches() {
// Can't change issuer.
transaction {
attachments(OBLIGATION_PROGRAM_ID)
input(OBLIGATION_PROGRAM_ID) { inState }
output(OBLIGATION_PROGRAM_ID) { outState `issued by` MINI_CORP }
command(MINI_CORP_PUBKEY) { Obligation.Commands.Move() }
@ -637,6 +668,7 @@ class ObligationTests {
}
// Can't mix currencies.
transaction {
attachments(OBLIGATION_PROGRAM_ID)
input(OBLIGATION_PROGRAM_ID) { inState }
output(OBLIGATION_PROGRAM_ID) { outState.copy(quantity = 80000, template = megaCorpDollarSettlement) }
output(OBLIGATION_PROGRAM_ID) { outState.copy(quantity = 20000, template = megaCorpPoundSettlement) }
@ -644,6 +676,7 @@ class ObligationTests {
this `fails with` "the amounts balance"
}
transaction {
attachments(OBLIGATION_PROGRAM_ID)
input(OBLIGATION_PROGRAM_ID) { inState }
input(OBLIGATION_PROGRAM_ID) {
inState.copy(
@ -658,6 +691,7 @@ class ObligationTests {
}
// Can't have superfluous input states from different issuers.
transaction {
attachments(OBLIGATION_PROGRAM_ID)
input(OBLIGATION_PROGRAM_ID) { inState }
input(OBLIGATION_PROGRAM_ID) { inState `issued by` MINI_CORP }
output(OBLIGATION_PROGRAM_ID) { outState }
@ -670,6 +704,7 @@ class ObligationTests {
fun `exit single product obligation`() {
// Single input/output straightforward case.
transaction {
attachments(OBLIGATION_PROGRAM_ID)
input(OBLIGATION_PROGRAM_ID) { inState }
output(OBLIGATION_PROGRAM_ID) { outState.copy(quantity = inState.quantity - 200.DOLLARS.quantity) }
@ -696,6 +731,8 @@ class ObligationTests {
fun `exit multiple product obligations`() {
// Multi-product case.
transaction {
attachments(OBLIGATION_PROGRAM_ID)
input(OBLIGATION_PROGRAM_ID) { inState.copy(template = inState.template.copy(acceptableIssuedProducts = megaIssuedPounds)) }
input(OBLIGATION_PROGRAM_ID) { inState.copy(template = inState.template.copy(acceptableIssuedProducts = megaIssuedDollars)) }
@ -717,6 +754,8 @@ class ObligationTests {
@Test
fun multiIssuer() {
transaction {
attachments(OBLIGATION_PROGRAM_ID)
// Gather 2000 dollars from two different issuers.
input(OBLIGATION_PROGRAM_ID) { inState }
input(OBLIGATION_PROGRAM_ID) { inState `issued by` MINI_CORP }
@ -747,6 +786,7 @@ class ObligationTests {
fun multiCurrency() {
// Check we can do an atomic currency trade tx.
transaction {
attachments(OBLIGATION_PROGRAM_ID)
val pounds = Obligation.State(Lifecycle.NORMAL, MINI_CORP, megaCorpPoundSettlement, 658.POUNDS.quantity, AnonymousParty(BOB_PUBKEY))
input(OBLIGATION_PROGRAM_ID) { inState `owned by` CHARLIE }
input(OBLIGATION_PROGRAM_ID) { pounds }

View File

@ -12,6 +12,8 @@ import net.corda.testing.getDefaultNotary
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNetwork.MockNode
import net.corda.testing.setCordappPackages
import net.corda.testing.unsetCordappPackages
import org.junit.After
import org.junit.Before
import org.junit.Test
@ -19,7 +21,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class CashExitFlowTests {
private val mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin())
private lateinit var mockNet : MockNetwork
private val initialBalance = 2000.DOLLARS
private val ref = OpaqueBytes.of(0x01)
private lateinit var bankOfCordaNode: StartedNode<MockNode>
@ -29,6 +31,8 @@ class CashExitFlowTests {
@Before
fun start() {
setCordappPackages("net.corda.finance.contracts.asset")
mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin())
val nodes = mockNet.createSomeNodes(1)
notaryNode = nodes.notaryNode
bankOfCordaNode = nodes.partyNodes[0]
@ -44,6 +48,7 @@ class CashExitFlowTests {
@After
fun cleanUp() {
mockNet.stopNodes()
unsetCordappPackages()
}
@Test

View File

@ -12,6 +12,7 @@ import net.corda.testing.getDefaultNotary
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNetwork.MockNode
import net.corda.testing.setCordappPackages
import org.junit.After
import org.junit.Before
import org.junit.Test
@ -19,7 +20,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class CashIssueFlowTests {
private val mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin())
private lateinit var mockNet : MockNetwork
private lateinit var bankOfCordaNode: StartedNode<MockNode>
private lateinit var bankOfCorda: Party
private lateinit var notaryNode: StartedNode<MockNode>
@ -27,6 +28,8 @@ class CashIssueFlowTests {
@Before
fun start() {
setCordappPackages("net.corda.finance.contracts.asset")
mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin())
val nodes = mockNet.createSomeNodes(1)
notaryNode = nodes.notaryNode
bankOfCordaNode = nodes.partyNodes[0]

View File

@ -17,6 +17,7 @@ import net.corda.testing.getDefaultNotary
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNetwork.MockNode
import net.corda.testing.setCordappPackages
import org.junit.After
import org.junit.Before
import org.junit.Test
@ -24,7 +25,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class CashPaymentFlowTests {
private val mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin())
private lateinit var mockNet : MockNetwork
private val initialBalance = 2000.DOLLARS
private val ref = OpaqueBytes.of(0x01)
private lateinit var bankOfCordaNode: StartedNode<MockNode>
@ -34,6 +35,8 @@ class CashPaymentFlowTests {
@Before
fun start() {
setCordappPackages("net.corda.finance.contracts.asset")
mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin())
val nodes = mockNet.createSomeNodes(1)
notaryNode = nodes.notaryNode
bankOfCordaNode = nodes.partyNodes[0]

View File

@ -10,6 +10,7 @@ import de.javakaffee.kryoserializers.ArraysAsListSerializer
import de.javakaffee.kryoserializers.BitSetSerializer
import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer
import de.javakaffee.kryoserializers.guava.*
import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.PrivacySalt
import net.corda.core.crypto.CompositeKey
import net.corda.core.identity.PartyAndCertificate
@ -38,6 +39,7 @@ import org.slf4j.Logger
import sun.security.ec.ECPublicKeyImpl
import sun.security.provider.certpath.X509CertPath
import java.io.BufferedInputStream
import java.io.ByteArrayOutputStream
import java.io.FileInputStream
import java.io.InputStream
import java.lang.reflect.Modifier.isPublic
@ -113,6 +115,9 @@ object DefaultKryoCustomizer {
// Don't deserialize PrivacySalt via its default constructor.
register(PrivacySalt::class.java, PrivacySaltSerializer)
// Used by the remote verifier, and will possibly be removed in future.
register(ContractAttachment::class.java, ContractAttachmentSerializer)
val customization = KryoSerializationCustomization(this)
pluginRegistries.forEach { it.customizeSerialization(customization) }
}
@ -170,4 +175,18 @@ object DefaultKryoCustomizer {
return PrivacySalt(input.readBytesWithLength())
}
}
private object ContractAttachmentSerializer : Serializer<ContractAttachment>() {
override fun write(kryo: Kryo, output: Output, obj: ContractAttachment) {
val buffer = ByteArrayOutputStream()
obj.attachment.open().use { it.copyTo(buffer) }
output.writeBytesWithLength(buffer.toByteArray())
output.writeString(obj.contract)
}
override fun read(kryo: Kryo, input: Input, type: Class<ContractAttachment>): ContractAttachment {
val attachment = GeneratedAttachment(input.readBytesWithLength())
return ContractAttachment(attachment, input.readString())
}
}
}

View File

@ -616,4 +616,4 @@ class ThrowableSerializer<T>(kryo: Kryo, type: Class<T>) : Serializer<Throwable>
}
private fun Throwable.setSuppressedToSentinel() = suppressedField.set(this, sentinelValue)
}
}

View File

@ -60,7 +60,7 @@ data class SerializationContextImpl(override val preferredSerializationVersion:
serializationContext.serviceHub.attachments.openAttachment(id)?.let { attachments += it } ?: run { missing += id }
}
missing.isNotEmpty() && throw MissingAttachmentsException(missing)
AttachmentsClassLoader(attachments)
AttachmentsClassLoader(attachments, parent = deserializationClassLoader)
})
} catch (e: ExecutionException) {
// Caught from within the cache get, so unwrap.

View File

@ -0,0 +1,17 @@
package net.corda.nodeapi
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.PartyAndReference
import net.corda.core.identity.Party
import net.corda.core.transactions.TransactionBuilder
/**
* This interface deliberately mirrors the one in the finance:isolated module.
* We will actually link [AnotherDummyContract] against this interface rather
* than the one inside isolated.jar, which means we won't need to use reflection
* to execute the contract's generateInitial() method.
*/
interface DummyContractBackdoor {
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder
fun inspectState(state: ContractState): Int
}

View File

@ -0,0 +1,74 @@
package net.corda.nodeapi.internal
import net.corda.core.contracts.*
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.testing.*
import net.corda.testing.node.MockServices
import org.junit.After
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
class AttachmentsClassLoaderStaticContractTests : TestDependencyInjectionBase() {
class AttachmentDummyContract : Contract {
companion object {
private val ATTACHMENT_PROGRAM_ID = "net.corda.nodeapi.internal.AttachmentsClassLoaderStaticContractTests\$AttachmentDummyContract"
}
data class State(val magicNumber: Int = 0) : ContractState {
override val participants: List<AbstractParty>
get() = listOf()
}
interface Commands : CommandData {
class Create : TypeOnlyCommandData(), Commands
}
override fun verify(tx: LedgerTransaction) {
// Always accepts.
}
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = State(magicNumber)
return TransactionBuilder(notary)
.withItems(StateAndContract(state, ATTACHMENT_PROGRAM_ID), Command(Commands.Create(), owner.party.owningKey))
}
}
private lateinit var serviceHub: MockServices
@Before
fun `create service hub`() {
serviceHub = MockServices(cordappPackages=listOf("net.corda.nodeapi.internal"))
}
@After
fun `clear packages`() {
unsetCordappPackages()
}
@Test
fun `test serialization of WireTransaction with statically loaded contract`() {
val tx = AttachmentDummyContract().generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val wireTransaction = tx.toWireTransaction(serviceHub)
val bytes = wireTransaction.serialize()
val copiedWireTransaction = bytes.deserialize()
assertEquals(1, copiedWireTransaction.outputs.size)
assertEquals(42, (copiedWireTransaction.outputs[0].data as AttachmentDummyContract.State).magicNumber)
}
@Test
fun `verify that contract DummyContract is in classPath`() {
val contractClass = Class.forName("net.corda.nodeapi.internal.AttachmentsClassLoaderStaticContractTests\$AttachmentDummyContract")
val contract = contractClass.newInstance() as Contract
assertNotNull(contract)
}
}

View File

@ -1,29 +1,30 @@
package net.corda.nodeapi
package net.corda.nodeapi.internal
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.internal.declaredField
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.serialization.*
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.OpaqueBytes
import net.corda.nodeapi.internal.AttachmentsClassLoader
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.nodeapi.DummyContractBackdoor
import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl
import net.corda.nodeapi.internal.serialization.attachmentsClassLoaderEnabledPropertyName
import net.corda.nodeapi.internal.serialization.withTokenContext
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.MEGA_CORP
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.kryoSpecific
import net.corda.testing.node.MockAttachmentStorage
import net.corda.testing.node.MockServices
import org.apache.commons.io.IOUtils
import org.junit.Assert
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
@ -31,83 +32,72 @@ import java.net.URL
import java.net.URLClassLoader
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
interface DummyContractBackdoor {
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder
fun inspectState(state: ContractState): Int
}
class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
companion object {
val ISOLATED_CONTRACTS_JAR_PATH: URL = AttachmentsClassLoaderTests::class.java.getResource("isolated.jar")
private val ISOLATED_CONTRACT_CLASS_NAME = "net.corda.finance.contracts.isolated.AnotherDummyContract"
private val ATTACHMENT_PROGRAM_ID = "net.corda.nodeapi.AttachmentsClassLoaderTests.AttachmentDummyContract"
private const val ISOLATED_CONTRACT_CLASS_NAME = "net.corda.finance.contracts.isolated.AnotherDummyContract"
private fun SerializationContext.withAttachmentStorage(attachmentStorage: AttachmentStorage): SerializationContext {
val serviceHub = mock<ServiceHub>()
whenever(serviceHub.attachments).thenReturn(attachmentStorage)
return this.withServiceHub(serviceHub)
}
private fun SerializationContext.withServiceHub(serviceHub: ServiceHub): SerializationContext {
return this.withTokenContext(SerializeAsTokenContextImpl(serviceHub) {}).withProperty(attachmentsClassLoaderEnabledPropertyName, true)
}
}
class AttachmentDummyContract : Contract {
data class State(val magicNumber: Int = 0) : ContractState {
override val participants: List<AbstractParty>
get() = listOf()
}
private lateinit var serviceHub: DummyServiceHub
interface Commands : CommandData {
class Create : TypeOnlyCommandData(), Commands
}
class DummyServiceHub : MockServices() {
override val cordappProvider: CordappProviderImpl
= CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH))).start(attachments)
override fun verify(tx: LedgerTransaction) {
// Always accepts.
}
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = State(magicNumber)
return TransactionBuilder(notary).withItems(StateAndContract(state, ATTACHMENT_PROGRAM_ID), Command(Commands.Create(), owner.party.owningKey))
}
private val cordapp get() = cordappProvider.cordapps.first()
val attachmentId get() = cordappProvider.getCordappAttachmentId(cordapp)!!
val appContext get() = cordappProvider.getAppContext(cordapp)
}
private fun importJar(storage: AttachmentStorage) = ISOLATED_CONTRACTS_JAR_PATH.openStream().use { storage.importAttachment(it) }
// These ClassLoaders work together to load 'AnotherDummyContract' in a disposable way, such that even though
// the class may be on the unit test class path (due to default IDE settings, etc), it won't be loaded into the
// regular app classloader but rather than ClassLoaderForTests. This helps keep our environment clean and
// ensures we have precise control over where it's loaded.
object FilteringClassLoader : ClassLoader() {
override fun loadClass(name: String, resolve: Boolean): Class<*>? {
return if ("AnotherDummyContract" in name) {
null
} else
super.loadClass(name, resolve)
@Throws(ClassNotFoundException::class)
override fun loadClass(name: String, resolve: Boolean): Class<*> {
if ("AnotherDummyContract" in name) {
throw ClassNotFoundException(name)
}
return super.loadClass(name, resolve)
}
}
class ClassLoaderForTests : URLClassLoader(arrayOf(ISOLATED_CONTRACTS_JAR_PATH), FilteringClassLoader)
@Before
fun `create service hub`() {
serviceHub = DummyServiceHub()
}
@Test
fun `dynamically load AnotherDummyContract from isolated contracts jar`() {
val child = ClassLoaderForTests()
ClassLoaderForTests().use { child ->
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as Contract
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as Contract
assertEquals("helloworld", contract.declaredField<Any?>("magicString").value)
assertEquals("helloworld", contract.declaredField<Any?>("magicString").value)
}
}
private fun fakeAttachment(filepath: String, content: String): ByteArray {
val bs = ByteArrayOutputStream()
val js = JarOutputStream(bs)
js.putNextEntry(ZipEntry(filepath))
js.writer().apply { append(content); flush() }
js.closeEntry()
js.close()
JarOutputStream(bs).use { js ->
js.putNextEntry(ZipEntry(filepath))
js.writer().apply { append(content); flush() }
js.closeEntry()
}
return bs.toByteArray()
}
@ -116,13 +106,12 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
attachment.extractFile(filepath, it)
return it.toByteArray()
}
}
@Test
fun `test MockAttachmentStorage open as jar`() {
val storage = MockAttachmentStorage()
val key = importJar(storage)
val storage = serviceHub.attachments
val key = serviceHub.attachmentId
val attachment = storage.openAttachment(key)!!
val jar = attachment.openAsJAR()
@ -132,9 +121,9 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
@Test
fun `test overlapping file exception`() {
val storage = MockAttachmentStorage()
val storage = serviceHub.attachments
val att0 = importJar(storage)
val att0 = serviceHub.attachmentId
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file.txt", "some other data")))
@ -145,9 +134,9 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
@Test
fun `basic`() {
val storage = MockAttachmentStorage()
val storage = serviceHub.attachments
val att0 = importJar(storage)
val att0 = serviceHub.attachmentId
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
@ -164,24 +153,23 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("\\folder1\\folderb\\file2.txt", "some other data")))
val data1a = readAttachment(storage.openAttachment(att1)!!, "/folder1/foldera/file1.txt")
Assert.assertArrayEquals("some data".toByteArray(), data1a)
assertArrayEquals("some data".toByteArray(), data1a)
val data1b = readAttachment(storage.openAttachment(att1)!!, "\\folder1\\foldera\\file1.txt")
Assert.assertArrayEquals("some data".toByteArray(), data1b)
assertArrayEquals("some data".toByteArray(), data1b)
val data2a = readAttachment(storage.openAttachment(att2)!!, "\\folder1\\folderb\\file2.txt")
Assert.assertArrayEquals("some other data".toByteArray(), data2a)
assertArrayEquals("some other data".toByteArray(), data2a)
val data2b = readAttachment(storage.openAttachment(att2)!!, "/folder1/folderb/file2.txt")
Assert.assertArrayEquals("some other data".toByteArray(), data2b)
assertArrayEquals("some other data".toByteArray(), data2b)
}
@Test
fun `loading class AnotherDummyContract`() {
val storage = MockAttachmentStorage()
val storage = serviceHub.attachments
val att0 = importJar(storage)
val att0 = serviceHub.attachmentId
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
@ -192,18 +180,11 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
assertEquals("helloworld", contract.declaredField<Any?>("magicString").value)
}
@Test
fun `verify that contract DummyContract is in classPath`() {
val contractClass = Class.forName("net.corda.nodeapi.AttachmentsClassLoaderTests\$AttachmentDummyContract")
val contract = contractClass.newInstance() as Contract
assertNotNull(contract)
}
private fun createContract2Cash(): Contract {
val cl = ClassLoaderForTests()
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, cl)
return contractClass.newInstance() as Contract
ClassLoaderForTests().use { cl ->
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, cl)
return contractClass.newInstance() as Contract
}
}
@Test
@ -212,9 +193,9 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
val bytes = contract.serialize()
val storage = MockAttachmentStorage()
val storage = serviceHub.attachments
val att0 = importJar(storage)
val att0 = serviceHub.attachmentId
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
@ -240,9 +221,9 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
val bytes = data.serialize(context = context2)
val storage = MockAttachmentStorage()
val storage = serviceHub.attachments
val att0 = importJar(storage)
val att0 = serviceHub.attachmentId
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
@ -291,39 +272,26 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
assertEquals(bytesSequence, copiedBytesSequence)
}
@Test
fun `test serialization of WireTransaction with statically loaded contract`() {
val tx = AttachmentDummyContract().generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val wireTransaction = tx.toWireTransaction()
val bytes = wireTransaction.serialize()
val copiedWireTransaction = bytes.deserialize()
assertEquals(1, copiedWireTransaction.outputs.size)
assertEquals(42, (copiedWireTransaction.outputs[0].data as AttachmentDummyContract.State).magicNumber)
}
@Test
fun `test serialization of WireTransaction with dynamically loaded contract`() {
val child = ClassLoaderForTests()
val child = serviceHub.appContext.classLoader
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val storage = MockAttachmentStorage()
val context = SerializationFactory.defaultFactory.defaultContext.withWhitelisted(contract.javaClass)
val context = SerializationFactory.defaultFactory.defaultContext
.withWhitelisted(contract.javaClass)
.withWhitelisted(Class.forName("$ISOLATED_CONTRACT_CLASS_NAME\$State", true, child))
.withWhitelisted(Class.forName("$ISOLATED_CONTRACT_CLASS_NAME\$Commands\$Create", true, child))
.withAttachmentStorage(storage)
.withServiceHub(serviceHub)
.withClassLoader(child)
// todo - think about better way to push attachmentStorage down to serializer
val bytes = run {
val attachmentRef = importJar(storage)
tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
val wireTransaction = tx.toWireTransaction(serializationContext = context)
wireTransaction.serialize()
val wireTransaction = tx.toWireTransaction(serviceHub, context)
wireTransaction.serialize(context = context)
}
val copiedWireTransaction = bytes.deserialize(context = context)
assertEquals(1, copiedWireTransaction.outputs.size)
// Contracts need to be loaded by the same classloader as the ContractState itself
// Contracts need to be loaded by the same classloader as the ContractState itself
val contractClassloader = copiedWireTransaction.getOutput(0).javaClass.classLoader
val contract2 = contractClassloader.loadClass(copiedWireTransaction.outputs.first().contract).newInstance() as DummyContractBackdoor
assertEquals(contract2.javaClass.classLoader, copiedWireTransaction.outputs[0].data.javaClass.classLoader)
@ -332,82 +300,82 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
@Test
fun `test deserialize of WireTransaction where contract cannot be found`() {
val child = ClassLoaderForTests()
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val storage = MockAttachmentStorage()
val context = SerializationFactory.defaultFactory.defaultContext.withWhitelisted(contract.javaClass)
.withWhitelisted(Class.forName("net.corda.finance.contracts.isolated.AnotherDummyContract\$State", true, child))
.withWhitelisted(Class.forName("net.corda.finance.contracts.isolated.AnotherDummyContract\$Commands\$Create", true, child))
.withAttachmentStorage(storage)
kryoSpecific<AttachmentsClassLoaderTests>("Kryo verifies/loads attachments on deserialization, whereas AMQP currently does not") {
ClassLoaderForTests().use { child ->
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
// todo - think about better way to push attachmentStorage down to serializer
val attachmentRef = importJar(storage)
val bytes = run {
val attachmentRef = serviceHub.attachmentId
val bytes = run {
val outboundContext = SerializationFactory.defaultFactory.defaultContext
.withServiceHub(serviceHub)
.withClassLoader(child)
val wireTransaction = tx.toWireTransaction(serviceHub, outboundContext)
wireTransaction.serialize(context = outboundContext)
}
// use empty attachmentStorage
tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
val e = assertFailsWith(MissingAttachmentsException::class) {
val mockAttStorage = MockAttachmentStorage()
val inboundContext = SerializationFactory.defaultFactory.defaultContext
.withAttachmentStorage(mockAttStorage)
.withAttachmentsClassLoader(listOf(attachmentRef))
bytes.deserialize(context = inboundContext)
val wireTransaction = tx.toWireTransaction(serializationContext = context)
wireTransaction.serialize()
}
// use empty attachmentStorage
val e = assertFailsWith(MissingAttachmentsException::class) {
val mockAttStorage = MockAttachmentStorage()
bytes.deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentStorage(mockAttStorage))
if(mockAttStorage.openAttachment(attachmentRef) == null) {
throw MissingAttachmentsException(listOf(attachmentRef))
if (mockAttStorage.openAttachment(attachmentRef) == null) {
throw MissingAttachmentsException(listOf(attachmentRef))
}
}
assertEquals(attachmentRef, e.ids.single())
}
}
assertEquals(attachmentRef, e.ids.single())
}
@Test
fun `test loading a class from attachment during deserialization`() {
val child = ClassLoaderForTests()
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val storage = MockAttachmentStorage()
val attachmentRef = importJar(storage)
val outboundContext = SerializationFactory.defaultFactory.defaultContext.withClassLoader(child)
// We currently ignore annotations in attachments, so manually whitelist.
val inboundContext = SerializationFactory
.defaultFactory
.defaultContext
.withWhitelisted(contract.javaClass)
.withAttachmentStorage(storage)
.withAttachmentsClassLoader(listOf(attachmentRef))
// Serialize with custom context to avoid populating the default context with the specially loaded class
val serialized = contract.serialize(context = outboundContext)
// Then deserialize with the attachment class loader associated with the attachment
serialized.deserialize(context = inboundContext)
}
@Test
fun `test loading a class with attachment missing during deserialization`() {
val child = ClassLoaderForTests()
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val storage = MockAttachmentStorage()
val attachmentRef = SecureHash.randomSHA256()
val outboundContext = SerializationFactory.defaultFactory.defaultContext.withClassLoader(child)
// Serialize with custom context to avoid populating the default context with the specially loaded class.
val serialized = contract.serialize(context = outboundContext)
// Then deserialize with the attachment class loader associated with the attachment.
val e = assertFailsWith(MissingAttachmentsException::class) {
ClassLoaderForTests().use { child ->
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val outboundContext = SerializationFactory.defaultFactory.defaultContext.withClassLoader(child)
val attachmentRef = serviceHub.attachmentId
// We currently ignore annotations in attachments, so manually whitelist.
val inboundContext = SerializationFactory
.defaultFactory
.defaultContext
.withWhitelisted(contract.javaClass)
.withAttachmentStorage(storage)
.withServiceHub(serviceHub)
.withAttachmentsClassLoader(listOf(attachmentRef))
// Serialize with custom context to avoid populating the default context with the specially loaded class
val serialized = contract.serialize(context = outboundContext)
// Then deserialize with the attachment class loader associated with the attachment
serialized.deserialize(context = inboundContext)
}
assertEquals(attachmentRef, e.ids.single())
}
@Test
fun `test loading a class with attachment missing during deserialization`() {
ClassLoaderForTests().use { child ->
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val attachmentRef = SecureHash.randomSHA256()
val outboundContext = SerializationFactory.defaultFactory.defaultContext.withClassLoader(child)
// Serialize with custom context to avoid populating the default context with the specially loaded class
val serialized = contract.serialize(context = outboundContext)
// Then deserialize with the attachment class loader associated with the attachment
val e = assertFailsWith(MissingAttachmentsException::class) {
// We currently ignore annotations in attachments, so manually whitelist.
val inboundContext = SerializationFactory
.defaultFactory
.defaultContext
.withWhitelisted(contract.javaClass)
.withServiceHub(serviceHub)
.withAttachmentsClassLoader(listOf(attachmentRef))
serialized.deserialize(context = inboundContext)
}
assertEquals(attachmentRef, e.ids.single())
}
}
}

View File

@ -10,8 +10,8 @@ import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationFactory
import net.corda.core.serialization.SerializedBytes
import net.corda.core.utilities.ByteSequence
import net.corda.nodeapi.AttachmentsClassLoaderTests
import net.corda.nodeapi.internal.AttachmentsClassLoader
import net.corda.nodeapi.internal.AttachmentsClassLoaderTests
import net.corda.testing.node.MockAttachmentStorage
import org.junit.Rule
import org.junit.Test
@ -82,11 +82,11 @@ class DefaultSerializableSerializer : Serializer<DefaultSerializable>() {
class CordaClassResolverTests {
val factory: SerializationFactory = object : SerializationFactory() {
override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented")
}
override fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented")
}
}

View File

@ -0,0 +1,108 @@
package net.corda.node.services
import net.corda.core.contracts.Contract
import net.corda.core.contracts.PartyAndReference
import net.corda.core.cordapp.CordappProvider
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.transpose
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.serialization.SerializationFactory
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.nodeapi.User
import net.corda.testing.DUMMY_BANK_A
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.driver.driver
import net.corda.testing.node.MockServices
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import java.net.URLClassLoader
import java.nio.file.Files
import kotlin.test.assertFailsWith
class AttachmentLoadingTests : TestDependencyInjectionBase() {
private class Services : MockServices() {
private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR))).start(attachments)
private val cordapp get() = provider.cordapps.first()
val attachmentId get() = provider.getCordappAttachmentId(cordapp)!!
val appContext get() = provider.getAppContext(cordapp)
override val cordappProvider: CordappProvider = provider
}
companion object {
private val isolatedJAR = this::class.java.getResource("isolated.jar")!!
private val ISOLATED_CONTRACT_ID = "net.corda.finance.contracts.isolated.AnotherDummyContract"
}
private lateinit var services: Services
@Before
fun setup() {
services = Services()
}
@Test
fun `test a wire transaction has loaded the correct attachment`() {
val appClassLoader = services.appContext.classLoader
val contractClass = appClassLoader.loadClass(ISOLATED_CONTRACT_ID).asSubclass(Contract::class.java)
val generateInitialMethod = contractClass.getDeclaredMethod("generateInitial", PartyAndReference::class.java, Integer.TYPE, Party::class.java)
val contract = contractClass.newInstance()
val txBuilder = generateInitialMethod.invoke(contract, PartyAndReference(DUMMY_BANK_A, OpaqueBytes(kotlin.ByteArray(1))), 1, DUMMY_NOTARY) as TransactionBuilder
val context = SerializationFactory.defaultFactory.defaultContext
.withClassLoader(appClassLoader)
val ledgerTx = txBuilder.toLedgerTransaction(services, context)
contract.verify(ledgerTx)
val actual = ledgerTx.attachments.first()
val expected = services.attachments.openAttachment(services.attachmentId)!!
assertEquals(expected, actual)
}
// TODO - activate this test
// @Test
fun `test that attachments retrieved over the network are not used for code`() {
driver(initialiseSerialization = false) {
val bankAName = CordaX500Name("BankA", "Zurich", "CH")
val bankBName = CordaX500Name("BankB", "Zurich", "CH")
// Copy the app jar to the first node. The second won't have it.
val path = (baseDirectory(bankAName.toString()) / "plugins").createDirectories() / "isolated.jar"
isolatedJAR.openStream().buffered().use { input ->
Files.newOutputStream(path).buffered().use { output ->
input.copyTo(output)
}
}
val admin = User("admin", "admin", permissions = setOf("ALL"))
val (bankA, bankB) = listOf(
startNode(providedName = bankAName, rpcUsers = listOf(admin)),
startNode(providedName = bankBName, rpcUsers = listOf(admin))
).transpose().getOrThrow() // Wait for all nodes to start up.
val clazz =
Class.forName("net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator", true, URLClassLoader(arrayOf(isolatedJAR)))
.asSubclass(FlowLogic::class.java)
try {
bankA.rpcClientToNode().start("admin", "admin").use { rpc ->
val proxy = rpc.proxy
val party = proxy.wellKnownPartyFromX500Name(bankBName)!!
assertFailsWith<Exception>("xxx") {
proxy.startFlowDynamic(clazz, party).returnValue.getOrThrow()
}
}
} finally {
bankA.stop()
bankB.stop()
}
}
}
}

View File

@ -1,6 +1,7 @@
package net.corda.node.services
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.CompositeKey
@ -43,6 +44,7 @@ class BFTNotaryServiceTests {
private val mockNet = MockNetwork()
private val node = mockNet.createNode(advertisedServices = ServiceInfo(NetworkMapService.type))
@After
fun stopNodes() {
mockNet.stopNodes()
@ -75,7 +77,7 @@ class BFTNotaryServiceTests {
val notary = node.services.getDefaultNotary()
val f = node.run {
val trivialTx = signInitialTransaction(notary) {
addOutputState(DummyContract.SingleOwnerState(owner = info.chooseIdentity()), DUMMY_PROGRAM_ID)
addOutputState(DummyContract.SingleOwnerState(owner = info.chooseIdentity()), DUMMY_PROGRAM_ID, AlwaysAcceptAttachmentConstraint)
}
// Create a new consensus while the redundant replica is sleeping:
services.startFlow(NotaryFlow.Client(trivialTx)).resultFuture
@ -100,7 +102,7 @@ class BFTNotaryServiceTests {
val notary = node.services.getDefaultNotary()
node.run {
val issueTx = signInitialTransaction(notary) {
addOutputState(DummyContract.SingleOwnerState(owner = info.chooseIdentity()), DUMMY_PROGRAM_ID)
addOutputState(DummyContract.SingleOwnerState(owner = info.chooseIdentity()), DUMMY_PROGRAM_ID, AlwaysAcceptAttachmentConstraint)
}
database.transaction {
services.recordTransactions(issueTx)

View File

@ -11,6 +11,7 @@ import net.corda.core.utilities.getOrThrow
import net.corda.finance.POUNDS
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.FlowPermissions.Companion.startFlowPermission
import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.nodeapi.User
import net.corda.testing.*
@ -21,7 +22,6 @@ import org.junit.Test
import rx.Observable
import java.util.*
import kotlin.test.assertEquals
import net.corda.node.services.FlowPermissions.Companion.startFlowPermission
class DistributedServiceTests : DriverBasedTest() {
lateinit var alice: NodeHandle
@ -30,7 +30,7 @@ class DistributedServiceTests : DriverBasedTest() {
lateinit var raftNotaryIdentity: Party
lateinit var notaryStateMachines: Observable<Pair<Party, StateMachineUpdate>>
override fun setup() = driver {
override fun setup() = driver(extraCordappPackagesToScan = listOf("net.corda.finance.contracts")) {
// Start Alice and 3 notaries in a RAFT cluster
val clusterSize = 3
val testUser = User("test", "test", permissions = setOf(

View File

@ -13,12 +13,12 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.StartedNode
import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.testing.DUMMY_BANK_A
import net.corda.testing.chooseIdentity
import net.corda.testing.*
import net.corda.testing.contracts.DUMMY_PROGRAM_ID
import net.corda.testing.contracts.DummyContract
import net.corda.testing.dummyCommand
import net.corda.testing.node.NodeBasedTest
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.util.*
import kotlin.test.assertEquals
@ -27,6 +27,16 @@ import kotlin.test.assertFailsWith
class RaftNotaryServiceTests : NodeBasedTest() {
private val notaryName = CordaX500Name(RaftValidatingNotaryService.type.id, "RAFT Notary Service", "London", "GB")
@Before
fun setup() {
setCordappPackages("net.corda.testing.contracts")
}
@After
fun tearDown() {
unsetCordappPackages()
}
@Test
fun `detect double spend`() {
val (bankA) = listOf(

View File

@ -62,7 +62,7 @@ class LargeTransactionsTest {
val bigFile2 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024 * 3, 1)
val bigFile3 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024 * 3, 2)
val bigFile4 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024 * 3, 3)
driver(startNodesInProcess = true) {
driver(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.testing.contracts")) {
val (alice, _, _) = aliceBobAndNotary()
alice.useRPC {
val hash1 = it.uploadAttachment(bigFile1.inputStream)

View File

@ -145,7 +145,7 @@ class SendMessageFlow(private val message: Message) : FlowLogic<SignedTransactio
val txBuilder = TransactionBuilder(notary).withItems(StateAndContract(messageState, MESSAGE_CONTRACT_PROGRAM_ID), txCommand)
progressTracker.currentStep = VERIFYING_TRANSACTION
txBuilder.toWireTransaction().toLedgerTransaction(serviceHub).verify()
txBuilder.toWireTransaction(serviceHub).toLedgerTransaction(serviceHub).verify()
progressTracker.currentStep = SIGNING_TRANSACTION
val signedTx = serviceHub.signInitialTransaction(txBuilder)

View File

@ -7,6 +7,7 @@ import com.google.common.util.concurrent.MoreExecutors
import net.corda.confidential.SwapIdentitiesFlow
import net.corda.confidential.SwapIdentitiesHandler
import net.corda.core.concurrent.CordaFuture
import net.corda.core.cordapp.CordappProvider
import net.corda.core.flows.*
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
@ -33,8 +34,8 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.debug
import net.corda.node.internal.classloading.requireAnnotation
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProvider
import net.corda.node.services.ContractUpgradeHandler
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.node.services.FinalityHandler
import net.corda.node.services.NotaryChangeHandler
import net.corda.node.services.api.*
@ -147,7 +148,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
protected val runOnStop = ArrayList<() -> Any?>()
protected lateinit var database: CordaPersistence
protected var dbCloser: (() -> Any?)? = null
lateinit var cordappProvider: CordappProvider
lateinit var cordappProvider: CordappProviderImpl
protected val _nodeReadyFuture = openFuture<Unit>()
/** Completes once the node has successfully registered with the network map service
@ -259,8 +260,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
check(myNotaryIdentity != null) { "Trying to install a notary service but no notary identity specified" }
val constructor = serviceClass.getDeclaredConstructor(ServiceHub::class.java, PublicKey::class.java).apply { isAccessible = true }
constructor.newInstance(services, myNotaryIdentity!!.owningKey)
}
else {
} else {
val constructor = serviceClass.getDeclaredConstructor(ServiceHub::class.java).apply { isAccessible = true }
constructor.newInstance(services)
}
@ -382,9 +382,10 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
*/
private fun makeServices(): MutableList<Any> {
checkpointStorage = DBCheckpointStorage()
cordappProvider = CordappProviderImpl(makeCordappLoader())
_services = ServiceHubInternalImpl()
attachments = NodeAttachmentService(services.monitoringService.metrics)
cordappProvider = CordappProvider(attachments, makeCordappLoader())
cordappProvider.start(attachments)
legalIdentity = obtainIdentity()
network = makeMessagingService(legalIdentity)
info = makeInfo(legalIdentity)
@ -393,16 +394,19 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
services.keyManagementService, services.identityService, platformClock, services.schedulerService,
services.auditService, services.monitoringService, services.networkMapCache, services.schemaService,
services.transactionVerifierService, services.validatedTransactions, services.contractUpgradeService,
services, this)
services, cordappProvider, this)
makeNetworkServices(tokenizableServices)
return tokenizableServices
}
private fun makeCordappLoader(): CordappLoader {
val scanPackages = System.getProperty("net.corda.node.cordapp.scan.packages")
return if (scanPackages != null) {
return if (CordappLoader.testPackages.isNotEmpty()) {
check(configuration.devMode) { "Package scanning can only occur in dev mode" }
CordappLoader.createDevMode(scanPackages)
CordappLoader.createWithTestPackages(CordappLoader.testPackages)
} else if (scanPackages != null) {
check(configuration.devMode) { "Package scanning can only occur in dev mode" }
CordappLoader.createWithTestPackages(scanPackages.split(","))
} else {
CordappLoader.createDefault(configuration.baseDirectory)
}
@ -646,8 +650,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
if (!keyStore.containsAlias(privateKeyAlias)) {
// TODO: Remove use of [ServiceIdentityGenerator.generateToDisk].
log.info("$privateKeyAlias not found in key store ${configuration.nodeKeystore}, generating fresh key!")
keyStore.signAndSaveNewKeyPair(name, privateKeyAlias, generateKeyPair())
log.info("$privateKeyAlias not found in key store ${configuration.nodeKeystore}, generating fresh key!")
keyStore.signAndSaveNewKeyPair(name, privateKeyAlias, generateKeyPair())
}
val (x509Cert, keys) = keyStore.certificateAndKeyPair(privateKeyAlias)
@ -713,6 +717,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
override val myInfo: NodeInfo get() = info
override val database: CordaPersistence get() = this@AbstractNode.database
override val configuration: NodeConfiguration get() = this@AbstractNode.configuration
override val cordappProvider: CordappProvider = this@AbstractNode.cordappProvider
override fun <T : SerializeAsToken> cordaService(type: Class<T>): T {
require(type.isAnnotationPresent(CordaService::class.java)) { "${type.name} is not a Corda service" }
@ -732,6 +737,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
super.recordTransactions(notifyVault, txs)
}
}
override fun jdbcSession(): Connection = database.createSession()
}

View File

@ -1,26 +0,0 @@
package net.corda.node.internal.cordapp
import net.corda.core.flows.FlowLogic
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializeAsToken
import java.net.URL
/**
* Defines a CorDapp
*
* @property contractClassNames List of contracts
* @property initiatedFlows List of initiatable flow classes
* @property rpcFlows List of RPC initiable flows classes
* @property servies List of RPC services
* @property plugins List of Corda plugin registries
* @property jarPath The path to the JAR for this CorDapp
*/
data class Cordapp(
val contractClassNames: List<String>,
val initiatedFlows: List<Class<out FlowLogic<*>>>,
val rpcFlows: List<Class<out FlowLogic<*>>>,
val services: List<Class<out SerializeAsToken>>,
val plugins: List<CordaPluginRegistry>,
val customSchemas: Set<MappedSchema>,
val jarPath: URL)

View File

@ -3,24 +3,34 @@ package net.corda.node.internal.cordapp
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult
import net.corda.core.contracts.Contract
import net.corda.core.contracts.UpgradedContract
import net.corda.core.cordapp.Cordapp
import net.corda.core.flows.ContractUpgradeFlow
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.StartableByRPC
import net.corda.core.internal.*
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.node.services.CordaService
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.utilities.loggerFor
import net.corda.node.internal.classloading.requireAnnotation
import java.io.File
import java.io.FileOutputStream
import java.lang.reflect.Modifier
import java.net.JarURLConnection
import java.net.URI
import java.net.URL
import java.net.URLClassLoader
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.attribute.FileTime
import java.time.Instant
import java.util.*
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
import kotlin.reflect.KClass
import kotlin.streams.toList
@ -45,35 +55,18 @@ class CordappLoader private constructor(private val cordappJarPaths: List<URL>)
* for classpath scanning.
*/
fun createDefault(baseDir: Path): CordappLoader {
val pluginsDir = baseDir / "plugins"
val pluginsDir = getPluginsPath(baseDir)
return CordappLoader(if (!pluginsDir.exists()) emptyList<URL>() else pluginsDir.list {
it.filter { it.isRegularFile() && it.toString().endsWith(".jar") }.map { it.toUri().toURL() }.toList()
})
}
/**
* Creates a dev mode CordappLoader intended to only be used in test environments.
*
* @param scanPackages list of packages to scan.
*/
fun createDevMode(scanPackages: String): CordappLoader {
val paths = scanPackages.split(",").flatMap { scanPackage ->
val resource = scanPackage.replace('.', '/')
this::class.java.classLoader.getResources(resource)
.asSequence()
.map {
val uri = if (it.protocol == "jar") {
(it.openConnection() as JarURLConnection).jarFileURL.toURI()
} else {
URI(it.toExternalForm().removeSuffix(resource))
}
uri.toURL()
}
.toList()
}
fun getPluginsPath(baseDir: Path): Path = baseDir / "plugins"
return CordappLoader(paths)
}
/**
* Create a dev mode CordappLoader for test environments
*/
fun createWithTestPackages(testPackages: List<String> = CordappLoader.testPackages) = CordappLoader(testPackages.flatMap(this::createScanPackage))
/**
* Creates a dev mode CordappLoader intended only to be used in test environments
@ -81,13 +74,62 @@ class CordappLoader private constructor(private val cordappJarPaths: List<URL>)
* @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection
*/
@VisibleForTesting
internal fun createDevMode(scanJars: List<URL>) = CordappLoader(scanJars)
fun createDevMode(scanJars: List<URL>) = CordappLoader(scanJars)
private fun createScanPackage(scanPackage: String): List<URL> {
val resource = scanPackage.replace('.', '/')
return this::class.java.classLoader.getResources(resource)
.asSequence()
.map { path ->
if (path.protocol == "jar") {
(path.openConnection() as JarURLConnection).jarFileURL.toURI()
} else {
createDevCordappJar(scanPackage, path, resource)
}.toURL()
}
.toList()
}
/** Takes a package of classes and creates a JAR from them - only use in tests */
private fun createDevCordappJar(scanPackage: String, path: URL, jarPackageName: String): URI {
if(!generatedCordapps.contains(path)) {
val cordappDir = File("build/tmp/generated-test-cordapps")
cordappDir.mkdirs()
val cordappJAR = File(cordappDir, "$scanPackage-${UUID.randomUUID()}.jar")
logger.info("Generating a test-only cordapp of classes discovered in $scanPackage at $cordappJAR")
FileOutputStream(cordappJAR).use {
JarOutputStream(it).use { jos ->
val scanDir = File(path.toURI())
scanDir.walkTopDown().forEach {
val entryPath = jarPackageName + "/" + scanDir.toPath().relativize(it.toPath()).toString().replace('\\', '/')
val time = FileTime.from(Instant.EPOCH)
val entry = ZipEntry(entryPath).setCreationTime(time).setLastAccessTime(time).setLastModifiedTime(time)
jos.putNextEntry(entry)
if (it.isFile) {
Files.copy(it.toPath(), jos)
}
jos.closeEntry()
}
}
}
generatedCordapps[path] = cordappJAR.toURI()
}
return generatedCordapps[path]!!
}
/**
* A list of test packages that will be scanned as CorDapps and compiled into CorDapp JARs for use in tests only
*/
@VisibleForTesting
var testPackages: List<String> = emptyList()
private val generatedCordapps = mutableMapOf<URL, URI>()
}
private fun loadCordapps(): List<Cordapp> {
return cordappJarPaths.map {
val scanResult = scanCordapp(it)
Cordapp(findContractClassNames(scanResult),
CordappImpl(findContractClassNames(scanResult),
findInitiatedFlows(scanResult),
findRPCFlows(scanResult),
findServices(scanResult),
@ -131,7 +173,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List<URL>)
}
private fun findContractClassNames(scanResult: ScanResult): List<String> {
return scanResult.getNamesOfClassesImplementing(Contract::class.java)
return (scanResult.getNamesOfClassesImplementing(Contract::class.java) + scanResult.getNamesOfClassesImplementing(UpgradedContract::class.java)).distinct()
}
private fun findPlugins(cordappJarPath: URL): List<CordaPluginRegistry> {

View File

@ -1,38 +0,0 @@
package net.corda.node.internal.cordapp
import com.google.common.collect.HashBiMap
import net.corda.core.crypto.SecureHash
import net.corda.core.node.services.AttachmentStorage
import java.util.*
/**
* Cordapp provider and store. For querying CorDapps for their attachment and vice versa.
*/
class CordappProvider(private val attachmentStorage: AttachmentStorage, private val cordappLoader: CordappLoader) {
/**
* Current known CorDapps loaded on this node
*/
val cordapps get() = cordappLoader.cordapps
private lateinit var cordappAttachments: HashBiMap<SecureHash, Cordapp>
/**
* Should only be called once from the initialisation routine of the node or tests
*/
fun start() {
cordappAttachments = HashBiMap.create(loadContractsIntoAttachmentStore())
}
/**
* Gets the attachment ID of this CorDapp. Only CorDapps with contracts have an attachment ID
*
* @param cordapp The cordapp to get the attachment ID
* @return An attachment ID if it exists, otherwise nothing
*/
fun getCordappAttachmentId(cordapp: Cordapp): SecureHash? = cordappAttachments.inverse().get(cordapp)
private fun loadContractsIntoAttachmentStore(): Map<SecureHash, Cordapp> {
val cordappsWithAttachments = cordapps.filter { !it.contractClassNames.isEmpty() }
val attachmentIds = cordappsWithAttachments.map { it.jarPath.openStream().use { attachmentStorage.importAttachment(it) } }
return attachmentIds.zip(cordappsWithAttachments).toMap()
}
}

View File

@ -0,0 +1,79 @@
package net.corda.node.internal.cordapp
import com.google.common.collect.HashBiMap
import net.corda.core.contracts.ContractClassName
import net.corda.core.crypto.SecureHash
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappContext
import net.corda.core.cordapp.CordappProvider
import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.SingletonSerializeAsToken
import java.net.URLClassLoader
/**
* Cordapp provider and store. For querying CorDapps for their attachment and vice versa.
*/
open class CordappProviderImpl(private val cordappLoader: CordappLoader) : SingletonSerializeAsToken(), CordappProvider {
override fun getAppContext(): CordappContext {
// TODO: Use better supported APIs in Java 9
Exception().stackTrace.forEach { stackFrame ->
val cordapp = getCordappForClass(stackFrame.className)
if(cordapp != null) {
return getAppContext(cordapp)
}
}
throw IllegalStateException("Not in an app context")
}
override fun getContractAttachmentID(contractClassName: ContractClassName): AttachmentId? {
return getCordappForClass(contractClassName)?.let(this::getCordappAttachmentId)
}
/**
* Current known CorDapps loaded on this node
*/
val cordapps get() = cordappLoader.cordapps
private lateinit var cordappAttachments: HashBiMap<SecureHash, Cordapp>
/**
* Should only be called once from the initialisation routine of the node or tests
*/
fun start(attachmentStorage: AttachmentStorage): CordappProviderImpl {
cordappAttachments = HashBiMap.create(loadContractsIntoAttachmentStore(attachmentStorage))
return this
}
/**
* Gets the attachment ID of this CorDapp. Only CorDapps with contracts have an attachment ID
*
* @param cordapp The cordapp to get the attachment ID
* @return An attachment ID if it exists, otherwise nothing
*/
fun getCordappAttachmentId(cordapp: Cordapp): SecureHash? = cordappAttachments.inverse().get(cordapp)
private fun loadContractsIntoAttachmentStore(attachmentStorage: AttachmentStorage): Map<SecureHash, Cordapp> {
val cordappsWithAttachments = cordapps.filter { !it.contractClassNames.isEmpty() }
val attachmentIds = cordappsWithAttachments.map { it.jarPath.openStream().use { attachmentStorage.importAttachment(it) } }
return attachmentIds.zip(cordappsWithAttachments).toMap()
}
/**
* Get the current cordapp context for the given CorDapp
*
* @param cordapp The cordapp to get the context for
* @return A cordapp context for the given CorDapp
*/
fun getAppContext(cordapp: Cordapp): CordappContext {
return CordappContext(cordapp, getCordappAttachmentId(cordapp), URLClassLoader(arrayOf(cordapp.jarPath), cordappLoader.appClassLoader))
}
/**
* Resolves a cordapp for the provided class or null if there isn't one
*
* @param className The class name
* @return cordapp A cordapp or null if no cordapp has the given class loaded
*/
fun getCordappForClass(className: String): Cordapp? = cordapps.find { it.cordappClasses.contains(className) }
}

View File

@ -58,7 +58,7 @@ class ContractUpgradeHandler(otherSide: FlowSession) : AbstractStateReplacementF
val authorisedUpgrade = serviceHub.contractUpgradeService.getAuthorisedContractUpgrade(oldStateAndRef.ref) ?:
throw IllegalStateException("Contract state upgrade is unauthorised. State hash : ${oldStateAndRef.ref}")
val proposedTx = stx.tx
val expectedTx = ContractUpgradeUtils.assembleBareTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt).toWireTransaction()
val expectedTx = ContractUpgradeUtils.assembleBareTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt).toWireTransaction(serviceHub)
requireThat {
"The instigator is one of the participants" using (initiatingSession.counterparty in oldStateAndRef.state.data.participants)
"The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification.name == authorisedUpgrade)

View File

@ -15,7 +15,6 @@ import net.corda.core.utilities.loggerFor
import net.corda.node.utilities.DatabaseTransactionManager
import net.corda.node.utilities.NODE_DATABASE_PREFIX
import java.io.*
import java.nio.file.FileAlreadyExistsException
import java.nio.file.Paths
import java.util.jar.JarInputStream
import javax.annotation.concurrent.ThreadSafe
@ -166,16 +165,14 @@ class NodeAttachmentService(metrics: MetricRegistry) : AttachmentStorage, Single
criteriaQuery.select(criteriaBuilder.count(criteriaQuery.from(NodeAttachmentService.DBAttachment::class.java)))
criteriaQuery.where(criteriaBuilder.equal(attachments.get<String>(DBAttachment::attId.name), id.toString()))
val count = session.createQuery(criteriaQuery).singleResult
if (count > 0) {
throw FileAlreadyExistsException(id.toString())
if (count == 0L) {
val attachment = NodeAttachmentService.DBAttachment(attId = id.toString(), content = bytes)
session.save(attachment)
attachmentCount.inc()
log.info("Stored new attachment $id")
}
val attachment = NodeAttachmentService.DBAttachment(attId = id.toString(), content = bytes)
session.save(attachment)
attachmentCount.inc()
log.info("Stored new attachment $id")
return id
}

View File

@ -62,13 +62,14 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
@Before
public void setUp() {
setCordappPackages("net.corda.testing.contracts", "net.corda.finance.contracts.asset");
ArrayList<KeyPair> keys = new ArrayList<>();
keys.add(getMEGA_CORP_KEY());
keys.add(getDUMMY_NOTARY_KEY());
Set<MappedSchema> requiredSchemas = Collections.singleton(CashSchemaV1.INSTANCE);
IdentityService identitySvc = makeTestIdentityService();
@SuppressWarnings("unchecked")
Pair<CordaPersistence, MockServices> databaseAndServices = makeTestDatabaseAndMockServices(requiredSchemas, keys, () -> identitySvc);
Pair<CordaPersistence, MockServices> databaseAndServices = makeTestDatabaseAndMockServices(requiredSchemas, keys, () -> identitySvc, Collections.EMPTY_LIST);
issuerServices = new MockServices(getDUMMY_CASH_ISSUER_KEY(), getBOC_KEY());
database = databaseAndServices.getFirst();
services = databaseAndServices.getSecond();
@ -78,6 +79,7 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
@After
public void cleanUp() throws IOException {
database.close();
unsetCordappPackages();
}
/**

View File

@ -32,12 +32,9 @@ import net.corda.nodeapi.internal.ServiceInfo
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.client.rpc.PermissionException
import net.corda.nodeapi.User
import net.corda.testing.chooseIdentity
import net.corda.testing.expect
import net.corda.testing.expectEvents
import net.corda.testing.*
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNetwork.MockNode
import net.corda.testing.sequence
import org.apache.commons.io.IOUtils
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.After
@ -68,6 +65,8 @@ class CordaRPCOpsImplTest {
@Before
fun setup() {
setCordappPackages("net.corda.finance.contracts.asset")
mockNet = MockNetwork()
val networkMap = mockNet.createNode(advertisedServices = ServiceInfo(NetworkMapService.type))
aliceNode = mockNet.createNode(networkMapAddress = networkMap.network.myAddress)
@ -86,6 +85,7 @@ class CordaRPCOpsImplTest {
@After
fun cleanUp() {
mockNet.stopNodes()
unsetCordappPackages()
}
@Test

View File

@ -1,7 +1,6 @@
package net.corda.node.cordapp
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.InitiatingFlow
import net.corda.node.internal.cordapp.CordappLoader
@ -15,7 +14,7 @@ class DummyFlow : FlowLogic<Unit>() {
}
@InitiatedBy(DummyFlow::class)
class LoaderTestFlow(unusedSession: FlowSession) : FlowLogic<Unit>() {
class LoaderTestFlow : FlowLogic<Unit>() {
override fun call() { }
}
@ -29,7 +28,7 @@ class CordappLoaderTest {
@Test
fun `test that classes that are in a cordapp are loaded`() {
val loader = CordappLoader.createDevMode("net.corda.node.cordapp")
val loader = CordappLoader.createWithTestPackages(listOf("net.corda.node.cordapp"))
val initiatedFlows = loader.cordapps.first().initiatedFlows
val expectedClass = loader.appClassLoader.loadClass("net.corda.node.cordapp.LoaderTestFlow").asSubclass(FlowLogic::class.java)
assertThat(initiatedFlows).contains(expectedClass)
@ -49,7 +48,7 @@ class CordappLoaderTest {
assertThat(actualCordapp.rpcFlows).contains(loader.appClassLoader.loadClass("net.corda.core.flows.ContractUpgradeFlow\$Initiate").asSubclass(FlowLogic::class.java))
assertThat(actualCordapp.services).isEmpty()
assertThat(actualCordapp.plugins).hasSize(1)
assertThat(actualCordapp.plugins.first().javaClass.name).isEqualTo("net.corda.finance.contracts.isolated.DummyPlugin")
assertThat(actualCordapp.plugins.first().javaClass.name).isEqualTo("net.corda.finance.contracts.isolated.IsolatedPlugin")
assertThat(actualCordapp.jarPath).isEqualTo(isolatedJAR)
}
}
}

View File

@ -0,0 +1,72 @@
package net.corda.node.cordapp
import net.corda.core.node.services.AttachmentStorage
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.testing.node.MockAttachmentStorage
import org.junit.Assert
import org.junit.Before
import org.junit.Test
class CordappProviderImplTests {
companion object {
private val isolatedJAR = this::class.java.getResource("isolated.jar")!!
private val emptyJAR = this::class.java.getResource("empty.jar")!!
}
private lateinit var attachmentStore: AttachmentStorage
@Before
fun setup() {
attachmentStore = MockAttachmentStorage()
}
@Test
fun `isolated jar is loaded into the attachment store`() {
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
val provider = CordappProviderImpl(loader)
provider.start(attachmentStore)
val maybeAttachmentId = provider.getCordappAttachmentId(provider.cordapps.first())
Assert.assertNotNull(maybeAttachmentId)
Assert.assertNotNull(attachmentStore.openAttachment(maybeAttachmentId!!))
}
@Test
fun `empty jar is not loaded into the attachment store`() {
val loader = CordappLoader.createDevMode(listOf(emptyJAR))
val provider = CordappProviderImpl(loader)
provider.start(attachmentStore)
Assert.assertNull(provider.getCordappAttachmentId(provider.cordapps.first()))
}
@Test
fun `test that we find a cordapp class that is loaded into the store`() {
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
val provider = CordappProviderImpl(loader)
val className = "net.corda.finance.contracts.isolated.AnotherDummyContract"
val expected = provider.cordapps.first()
val actual = provider.getCordappForClass(className)
Assert.assertNotNull(actual)
Assert.assertEquals(expected, actual)
}
@Test
fun `test that we find an attachment for a cordapp contrat class`() {
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
val provider = CordappProviderImpl(loader)
val className = "net.corda.finance.contracts.isolated.AnotherDummyContract"
provider.start(attachmentStore)
val expected = provider.getAppContext(provider.cordapps.first()).attachmentId
val actual = provider.getContractAttachmentID(className)
Assert.assertNotNull(actual)
Assert.assertEquals(actual!!, expected)
}
}

View File

@ -1,35 +0,0 @@
package net.corda.node.cordapp
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProvider
import net.corda.testing.node.MockAttachmentStorage
import org.junit.Assert
import org.junit.Test
class CordappProviderTests {
@Test
fun `isolated jar is loaded into the attachment store`() {
val attachmentStore = MockAttachmentStorage()
val isolatedJAR = this::class.java.getResource("isolated.jar")!!
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
val provider = CordappProvider(attachmentStore, loader)
provider.start()
val maybeAttachmentId = provider.getCordappAttachmentId(provider.cordapps.first())
Assert.assertNotNull(maybeAttachmentId)
Assert.assertNotNull(attachmentStore.openAttachment(maybeAttachmentId!!))
}
@Test
fun `empty jar is not loaded into the attachment store`() {
val attachmentStore = MockAttachmentStorage()
val isolatedJAR = this::class.java.getResource("empty.jar")!!
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
val provider = CordappProvider(attachmentStore, loader)
provider.start()
Assert.assertNull(provider.getCordappAttachmentId(provider.cordapps.first()))
}
}

View File

@ -67,10 +67,11 @@ import kotlin.test.assertTrue
* We assume that Alice and Bob already found each other via some market, and have agreed the details already.
*/
class TwoPartyTradeFlowTests {
lateinit var mockNet: MockNetwork
private lateinit var mockNet: MockNetwork
@Before
fun before() {
setCordappPackages("net.corda.finance.contracts")
LogHelper.setLevel("platform.trade", "core.contract.TransactionGroup", "recordingmap")
}
@ -78,6 +79,7 @@ class TwoPartyTradeFlowTests {
fun after() {
mockNet.stopNodes()
LogHelper.reset("platform.trade", "core.contract.TransactionGroup", "recordingmap")
unsetCordappPackages()
}
@Test
@ -351,8 +353,9 @@ class TwoPartyTradeFlowTests {
attachment(ByteArrayInputStream(stream.toByteArray()))
}
val bobsFakeCash = fillUpForBuyer(false, issuer, AnonymousParty(bobNode.info.chooseIdentity().owningKey),
notary).second
val bobsFakeCash = bobNode.database.transaction {
fillUpForBuyer(false, issuer, AnonymousParty(bobNode.info.chooseIdentity().owningKey), notary)
}.second
val bobsSignedTxns = insertFakeTransactions(bobsFakeCash, bobNode, notaryNode, bankNode)
val alicesFakePaper = aliceNode.database.transaction {
fillUpForSeller(false, issuer, aliceNode.info.chooseIdentity(),
@ -458,8 +461,9 @@ class TwoPartyTradeFlowTests {
}
val bobsKey = bobNode.services.keyManagementService.keys.single()
val bobsFakeCash = fillUpForBuyer(false, issuer, AnonymousParty(bobsKey),
notary).second
val bobsFakeCash = bobNode.database.transaction {
fillUpForBuyer(false, issuer, AnonymousParty(bobsKey), notary)
}.second
insertFakeTransactions(bobsFakeCash, bobNode, notaryNode, bankNode)
val alicesFakePaper = aliceNode.database.transaction {

View File

@ -15,11 +15,9 @@ import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.nodeapi.internal.ServiceInfo
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.*
import net.corda.testing.contracts.DUMMY_PROGRAM_ID
import net.corda.testing.chooseIdentity
import net.corda.testing.contracts.DummyContract
import net.corda.testing.dummyCommand
import net.corda.testing.getTestPartyAndCertificate
import net.corda.testing.node.MockNetwork
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.After
@ -41,6 +39,7 @@ class NotaryChangeTests {
@Before
fun setUp() {
setCordappPackages("net.corda.testing.contracts")
mockNet = MockNetwork()
oldNotaryNode = mockNet.createNode(
legalName = DUMMY_NOTARY.name,
@ -58,6 +57,7 @@ class NotaryChangeTests {
@After
fun cleanUp() {
mockNet.stopNodes()
unsetCordappPackages()
}
@Test
@ -151,7 +151,7 @@ class NotaryChangeTests {
}
val stx = node.services.signInitialTransaction(tx)
node.services.recordTransactions(stx)
return tx.toWireTransaction()
return tx.toWireTransaction(node.services)
}
// TODO: Add more test cases once we have a general flow/service exception handling mechanism:
@ -180,8 +180,7 @@ fun issueMultiPartyState(nodeA: StartedNode<*>, nodeB: StartedNode<*>, notaryNod
val stx = notaryNode.services.addSignature(signedByAB, notaryIdentity.owningKey)
nodeA.services.recordTransactions(stx)
nodeB.services.recordTransactions(stx)
val stateAndRef = StateAndRef(state, StateRef(stx.id, 0))
return stateAndRef
return StateAndRef(state, StateRef(stx.id, 0))
}
fun issueInvalidState(node: StartedNode<*>, notary: Party): StateAndRef<*> {

View File

@ -13,6 +13,8 @@ import net.corda.core.node.services.VaultService
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.days
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.services.persistence.DBCheckpointStorage
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
@ -43,19 +45,19 @@ import java.util.concurrent.TimeUnit
import kotlin.test.assertTrue
class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
val realClock: Clock = Clock.systemUTC()
val stoppedClock: Clock = Clock.fixed(realClock.instant(), realClock.zone)
val testClock = TestClock(stoppedClock)
private val realClock: Clock = Clock.systemUTC()
private val stoppedClock: Clock = Clock.fixed(realClock.instant(), realClock.zone)
private val testClock = TestClock(stoppedClock)
val schedulerGatedExecutor = AffinityExecutor.Gate(true)
private val schedulerGatedExecutor = AffinityExecutor.Gate(true)
lateinit var services: MockServiceHubInternal
private lateinit var services: MockServiceHubInternal
lateinit var scheduler: NodeSchedulerService
lateinit var smmExecutor: AffinityExecutor.ServiceAffinityExecutor
lateinit var database: CordaPersistence
lateinit var countDown: CountDownLatch
lateinit var smmHasRemovedAllFlows: CountDownLatch
private lateinit var scheduler: NodeSchedulerService
private lateinit var smmExecutor: AffinityExecutor.ServiceAffinityExecutor
private lateinit var database: CordaPersistence
private lateinit var countDown: CountDownLatch
private lateinit var smmHasRemovedAllFlows: CountDownLatch
var calls: Int = 0
@ -70,6 +72,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
@Before
fun setup() {
setCordappPackages("net.corda.testing.contracts")
initialiseTestSerialization()
countDown = CountDownLatch(1)
smmHasRemovedAllFlows = CountDownLatch(1)
@ -95,6 +98,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
network = mockMessagingService), TestReference {
override val vaultService: VaultService = NodeVaultService(this)
override val testReference = this@NodeSchedulerServiceTest
override val cordappProvider: CordappProviderImpl = CordappProviderImpl(CordappLoader.createWithTestPackages()).start(attachments)
}
smmExecutor = AffinityExecutor.ServiceAffinityExecutor("test", 1)
scheduler = NodeSchedulerService(services, schedulerGatedExecutor, serverThread = smmExecutor)
@ -120,6 +124,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
smmExecutor.awaitTermination(60, TimeUnit.SECONDS)
database.close()
resetTestSerialization()
unsetCordappPackages()
}
class TestState(val flowLogicRef: FlowLogicRef, val instant: Instant, val myIdentity: Party) : LinearState, SchedulableState {

View File

@ -19,14 +19,13 @@ import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.statemachine.StateMachineManager
import net.corda.node.services.transactions.ValidatingNotaryService
import net.corda.nodeapi.internal.ServiceInfo
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.chooseIdentity
import net.corda.testing.*
import net.corda.testing.contracts.DUMMY_PROGRAM_ID
import net.corda.testing.dummyCommand
import net.corda.testing.getDefaultNotary
import net.corda.testing.node.MockNetwork
import org.junit.After
import org.junit.Assert.assertTrue
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import java.time.Instant
@ -34,7 +33,7 @@ import kotlin.test.assertEquals
class ScheduledFlowTests {
companion object {
val PAGE_SIZE = 20
const val PAGE_SIZE = 20
val SORTING = Sort(listOf(Sort.SortColumn(SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_TXN_ID), Sort.Direction.DESC)))
}
@ -97,6 +96,7 @@ class ScheduledFlowTests {
@Before
fun setup() {
setCordappPackages("net.corda.testing.contracts")
mockNet = MockNetwork(threadPerNode = true)
notaryNode = mockNet.createNode(
legalName = DUMMY_NOTARY.name,
@ -114,6 +114,7 @@ class ScheduledFlowTests {
@After
fun cleanUp() {
mockNet.stopNodes()
unsetCordappPackages()
}
@Test
@ -135,7 +136,7 @@ class ScheduledFlowTests {
nodeB.services.vaultQueryService.queryBy<ScheduledState>().states.single()
}
assertEquals(1, countScheduledFlows)
assertEquals(stateFromA, stateFromB, "Must be same copy on both nodes")
assertEquals("Must be same copy on both nodes", stateFromA, stateFromB)
assertTrue("Must be processed", stateFromB.state.data.processed)
}
@ -159,7 +160,7 @@ class ScheduledFlowTests {
val statesFromB: List<StateAndRef<ScheduledState>> = nodeB.database.transaction {
queryStatesWithPaging(nodeB.services.vaultQueryService)
}
assertEquals(2 * N, statesFromA.count(), "Expect all states to be present")
assertEquals("Expect all states to be present",2 * N, statesFromA.count())
statesFromA.forEach { ref ->
if (ref !in statesFromB) {
throw IllegalStateException("State $ref is only present on node A.")
@ -170,7 +171,7 @@ class ScheduledFlowTests {
throw IllegalStateException("State $ref is only present on node B.")
}
}
assertEquals(statesFromA, statesFromB, "Expect identical data on both nodes")
assertEquals("Expect identical data on both nodes", statesFromA, statesFromB)
assertTrue("Expect all states have run the scheduled task", statesFromB.all { it.state.data.processed })
}

View File

@ -73,6 +73,7 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() {
@Before
fun setUp() {
setCordappPackages("net.corda.testing.contracts", "net.corda.finance.contracts.asset")
issuerServices = MockServices(DUMMY_CASH_ISSUER_KEY, BOB_KEY, BOC_KEY)
val dataSourceProps = makeTestDataSourceProperties()
val defaultDatabaseProperties = makeTestDatabaseProperties()
@ -105,6 +106,7 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() {
@After
fun cleanUp() {
database.close()
unsetCordappPackages()
}
private fun setUpDb() {

View File

@ -19,6 +19,7 @@ import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
import net.corda.testing.node.MockServices.Companion.makeTestIdentityService
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import java.nio.charset.Charset
import java.nio.file.FileAlreadyExistsException
@ -77,6 +78,7 @@ class NodeAttachmentStorageTest {
}
}
@Ignore("We need to be able to restart nodes - make importing attachments idempotent?")
@Test
fun `duplicates not allowed`() {
val testJar = makeTestJar()

View File

@ -66,7 +66,7 @@ class FlowFrameworkTests {
}
}
private val mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin())
private lateinit var mockNet: MockNetwork
private val receivedSessionMessages = ArrayList<SessionTransfer>()
private lateinit var node1: StartedNode<MockNode>
private lateinit var node2: StartedNode<MockNode>
@ -77,6 +77,8 @@ class FlowFrameworkTests {
@Before
fun start() {
setCordappPackages("net.corda.finance.contracts", "net.corda.testing.contracts")
mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin())
node1 = mockNet.createNode(advertisedServices = ServiceInfo(NetworkMapService.type))
node2 = mockNet.createNode(networkMapAddress = node1.network.myAddress)
@ -105,6 +107,7 @@ class FlowFrameworkTests {
fun cleanUp() {
mockNet.stopNodes()
receivedSessionMessages.clear()
unsetCordappPackages()
}
@Test

View File

@ -15,10 +15,8 @@ import net.corda.core.utilities.seconds
import net.corda.node.internal.StartedNode
import net.corda.nodeapi.internal.ServiceInfo
import net.corda.node.services.network.NetworkMapService
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.chooseIdentity
import net.corda.testing.*
import net.corda.testing.contracts.DummyContract
import net.corda.testing.dummyCommand
import net.corda.testing.getDefaultNotary
import net.corda.testing.node.MockNetwork
import org.assertj.core.api.Assertions.assertThat
@ -38,6 +36,7 @@ class NotaryServiceTests {
@Before
fun setup() {
setCordappPackages("net.corda.testing.contracts")
mockNet = MockNetwork()
notaryNode = mockNet.createNode(
legalName = DUMMY_NOTARY.name,
@ -51,6 +50,7 @@ class NotaryServiceTests {
@After
fun cleanUp() {
mockNet.stopNodes()
unsetCordappPackages()
}
@Test

View File

@ -16,9 +16,7 @@ import net.corda.node.internal.StartedNode
import net.corda.nodeapi.internal.ServiceInfo
import net.corda.node.services.issueInvalidState
import net.corda.node.services.network.NetworkMapService
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.MEGA_CORP_KEY
import net.corda.testing.chooseIdentity
import net.corda.testing.*
import net.corda.testing.contracts.DummyContract
import net.corda.testing.dummyCommand
import net.corda.testing.getDefaultNotary
@ -39,6 +37,7 @@ class ValidatingNotaryServiceTests {
@Before
fun setup() {
setCordappPackages("net.corda.testing.contracts")
mockNet = MockNetwork()
notaryNode = mockNet.createNode(
legalName = DUMMY_NOTARY.name,
@ -53,6 +52,7 @@ class ValidatingNotaryServiceTests {
@After
fun cleanUp() {
mockNet.stopNodes()
unsetCordappPackages()
}
@Test

View File

@ -53,6 +53,7 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
@Before
fun setUp() {
setCordappPackages("net.corda.finance.contracts.asset")
LogHelper.setLevel(NodeVaultService::class)
val databaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(BOC_KEY, DUMMY_CASH_ISSUER_KEY),
customSchemas = setOf(CashSchemaV1))
@ -65,6 +66,7 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
fun tearDown() {
database.close()
LogHelper.reset(NodeVaultService::class)
unsetCordappPackages()
}
@Suspendable
@ -509,7 +511,7 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
val issueTx = TransactionBuilder(services.myInfo.chooseIdentity()).apply {
Cash().generateIssue(this,
amount, anonymousIdentity.party, services.myInfo.chooseIdentity())
}.toWireTransaction()
}.toWireTransaction(services)
val cashState = StateAndRef(issueTx.outputs.single(), StateRef(issueTx.id, 0))
database.transaction { service.notify(issueTx) }
@ -518,7 +520,7 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
database.transaction {
val moveTx = TransactionBuilder(services.myInfo.chooseIdentity()).apply {
Cash.generateSpend(services, this, Amount(1000, GBP), thirdPartyIdentity)
}.toWireTransaction()
}.toWireTransaction(services)
service.notify(moveTx)
}
val expectedMoveUpdate = Vault.Update(setOf(cashState), emptySet(), null)
@ -563,7 +565,7 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
val moveTx = database.transaction {
TransactionBuilder(newNotary).apply {
Cash.generateSpend(services, this, Amount(1000, GBP), thirdPartyIdentity)
}.toWireTransaction()
}.toWireTransaction(services)
}
database.transaction {

View File

@ -46,20 +46,22 @@ import java.util.*
class VaultQueryTests : TestDependencyInjectionBase() {
lateinit var services: MockServices
lateinit var notaryServices: MockServices
val vaultSvc: VaultService get() = services.vaultService
val vaultQuerySvc: VaultQueryService get() = services.vaultQueryService
val identitySvc: IdentityService = makeTestIdentityService()
lateinit var database: CordaPersistence
private lateinit var services: MockServices
private lateinit var notaryServices: MockServices
private val vaultSvc: VaultService get() = services.vaultService
private val vaultQuerySvc: VaultQueryService get() = services.vaultQueryService
private val identitySvc: IdentityService = makeTestIdentityService()
private lateinit var database: CordaPersistence
// test cash notary
val CASH_NOTARY_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(21)) }
val CASH_NOTARY: Party get() = Party(CordaX500Name(organisation = "Cash Notary Service", locality = "Zurich", country = "CH"), CASH_NOTARY_KEY.public)
val CASH_NOTARY_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(CASH_NOTARY.nameOrNull(), CASH_NOTARY_KEY.public)
private val CASH_NOTARY_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(21)) }
private val CASH_NOTARY: Party get() = Party(CordaX500Name(organisation = "Cash Notary Service", locality = "Zurich", country = "CH"), CASH_NOTARY_KEY.public)
private val CASH_NOTARY_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(CASH_NOTARY.nameOrNull(), CASH_NOTARY_KEY.public)
@Before
fun setUp() {
setCordappPackages("net.corda.testing.contracts", "net.corda.finance.contracts")
// register additional identities
identitySvc.verifyAndRegisterIdentity(CASH_NOTARY_IDENTITY)
identitySvc.verifyAndRegisterIdentity(BOC_IDENTITY)
@ -74,6 +76,7 @@ class VaultQueryTests : TestDependencyInjectionBase() {
@After
fun tearDown() {
database.close()
unsetCordappPackages()
}
/**
@ -1100,12 +1103,12 @@ class VaultQueryTests : TestDependencyInjectionBase() {
val states = result.states
val metadata = result.statesMetadata
for (i in 0..states.size - 1) {
for (i in 0 until states.size) {
println("${states[i].ref} : ${metadata[i].contractStateClassName}, ${metadata[i].status}, ${metadata[i].consumedTime}")
}
assertThat(states).hasSize(20)
assertThat(metadata.first().contractStateClassName).isEqualTo("net.corda.testing.contracts.DummyLinearContract\$State")
assertThat(metadata.first().contractStateClassName).isEqualTo("$DUMMY_LINEAR_CONTRACT_PROGRAM_ID\$State")
assertThat(metadata.first().status).isEqualTo(Vault.StateStatus.UNCONSUMED) // 0 = UNCONSUMED
assertThat(metadata.last().contractStateClassName).isEqualTo("net.corda.finance.contracts.asset.Cash\$State")
assertThat(metadata.last().status).isEqualTo(Vault.StateStatus.CONSUMED) // 1 = CONSUMED

View File

@ -40,22 +40,26 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
val vault: VaultService get() = services.vaultService
val vaultQuery: VaultQueryService get() = services.vaultQueryService
lateinit var database: CordaPersistence
val notaryServices = MockServices(DUMMY_NOTARY_KEY)
lateinit var notaryServices: MockServices
@Before
fun setUp() {
setCordappPackages("net.corda.testing.contracts", "net.corda.finance.contracts.asset")
LogHelper.setLevel(VaultWithCashTest::class)
val databaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(DUMMY_CASH_ISSUER_KEY, DUMMY_NOTARY_KEY),
customSchemas = setOf(CashSchemaV1))
database = databaseAndServices.first
services = databaseAndServices.second
issuerServices = MockServices(DUMMY_CASH_ISSUER_KEY, MEGA_CORP_KEY)
notaryServices = MockServices(DUMMY_NOTARY_KEY)
}
@After
fun tearDown() {
LogHelper.reset(VaultWithCashTest::class)
database.close()
unsetCordappPackages()
}
@Test

View File

@ -185,8 +185,8 @@ val ATTACHMENT_PROGRAM_ID = "net.corda.attachmentdemo.AttachmentContract"
class AttachmentContract : Contract {
override fun verify(tx: LedgerTransaction) {
val state = tx.outputsOfType<AttachmentContract.State>().single()
val attachment = tx.attachments.single()
require(state.hash == attachment.id)
// we check that at least one has the matching hash, the other will be the contract
require(tx.attachments.any { it.id == state.hash })
}
object Command : TypeOnlyCommandData()

View File

@ -63,7 +63,7 @@ open class RatesFixFlow(protected val tx: TransactionBuilder,
tx.addCommand(fix, oracle.owningKey)
beforeSigning(fix)
progressTracker.currentStep = SIGNING
val mtx = tx.toWireTransaction().buildFilteredTransaction(Predicate { filtering(it) })
val mtx = tx.toWireTransaction(serviceHub).buildFilteredTransaction(Predicate { filtering(it) })
return subFlow(FixSignFlow(tx, oracle, mtx))
}
// DOCEND 2
@ -120,7 +120,7 @@ open class RatesFixFlow(protected val tx: TransactionBuilder,
val resp = oracleSession.sendAndReceive<TransactionSignature>(SignRequest(partialMerkleTx))
return resp.unwrap { sig ->
check(oracleSession.counterparty.owningKey.isFulfilledBy(listOf(sig.by)))
tx.toWireTransaction().checkSignature(sig)
tx.toWireTransaction(serviceHub).checkSignature(sig)
sig
}
}

View File

@ -45,7 +45,7 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
private val DUMMY_CASH_ISSUER_KEY = generateKeyPair()
private val DUMMY_CASH_ISSUER = Party(CordaX500Name(organisation = "Cash issuer", locality = "London", country = "GB"), DUMMY_CASH_ISSUER_KEY.public)
private val services = MockServices(DUMMY_CASH_ISSUER_KEY, MEGA_CORP_KEY)
private val services = MockServices(listOf("net.corda.finance.contracts.asset"), DUMMY_CASH_ISSUER_KEY, MEGA_CORP_KEY)
private lateinit var oracle: NodeInterestRates.Oracle
private lateinit var database: CordaPersistence
@ -61,6 +61,7 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
@Before
fun setUp() {
setCordappPackages("net.corda.finance.contracts")
database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), createIdentityService = ::makeTestIdentityService)
database.transaction {
oracle = NodeInterestRates.Oracle(services).apply { knownFixes = TEST_DATA }
@ -70,6 +71,7 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
@After
fun tearDown() {
database.close()
unsetCordappPackages()
}
@Test
@ -123,7 +125,7 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
fun `refuse to sign with no relevant commands`() {
database.transaction {
val tx = makeFullTx()
val wtx1 = tx.toWireTransaction()
val wtx1 = tx.toWireTransaction(services)
fun filterAllOutputs(elem: Any): Boolean {
return when (elem) {
is TransactionState<ContractState> -> true
@ -134,7 +136,7 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
val ftx1 = wtx1.buildFilteredTransaction(Predicate(::filterAllOutputs))
assertFailsWith<IllegalArgumentException> { oracle.sign(ftx1) }
tx.addCommand(Cash.Commands.Move(), ALICE_PUBKEY)
val wtx2 = tx.toWireTransaction()
val wtx2 = tx.toWireTransaction(services)
val ftx2 = wtx2.buildFilteredTransaction(Predicate { x -> filterCmds(x) })
assertFalse(wtx1.id == wtx2.id)
assertFailsWith<IllegalArgumentException> { oracle.sign(ftx2) }
@ -148,7 +150,7 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
val fix = oracle.query(listOf(NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M"))).first()
tx.addCommand(fix, services.myInfo.chooseIdentity().owningKey)
// Sign successfully.
val wtx = tx.toWireTransaction()
val wtx = tx.toWireTransaction(services)
val ftx = wtx.buildFilteredTransaction(Predicate { fixCmdFilter(it) })
val signature = oracle.sign(ftx)
wtx.checkSignature(signature)
@ -162,7 +164,7 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
val fixOf = NodeInterestRates.parseFixOf("LIBOR 2016-03-16 1M")
val badFix = Fix(fixOf, BigDecimal("0.6789"))
tx.addCommand(badFix, services.myInfo.chooseIdentity().owningKey)
val wtx = tx.toWireTransaction()
val wtx = tx.toWireTransaction(services)
val ftx = wtx.buildFilteredTransaction(Predicate { fixCmdFilter(it) })
val e1 = assertFailsWith<NodeInterestRates.UnknownFix> { oracle.sign(ftx) }
assertEquals(fixOf, e1.fix)
@ -182,7 +184,7 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
}
}
tx.addCommand(fix, services.myInfo.chooseIdentity().owningKey)
val wtx = tx.toWireTransaction()
val wtx = tx.toWireTransaction(services)
val ftx = wtx.buildFilteredTransaction(Predicate(::filtering))
assertFailsWith<IllegalArgumentException> { oracle.sign(ftx) }
}
@ -191,7 +193,7 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
@Test
fun `empty partial transaction to sign`() {
val tx = makeFullTx()
val wtx = tx.toWireTransaction()
val wtx = tx.toWireTransaction(services)
val ftx = wtx.buildFilteredTransaction(Predicate { false })
assertFailsWith<IllegalArgumentException> { oracle.sign(ftx) } // It throws failed requirement (as it is empty there is no command to check and sign).
}
@ -216,7 +218,7 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
mockNet.runNetwork()
future.getOrThrow()
// We should now have a valid fix of our tx from the oracle.
val fix = tx.toWireTransaction().commands.map { it.value as Fix }.first()
val fix = tx.toWireTransaction(services).commands.map { it.value as Fix }.first()
assertEquals(fixOf, fix.of)
assertEquals(BigDecimal("0.678"), fix.value)
mockNet.stopNodes()

View File

@ -7,9 +7,34 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.seconds
import net.corda.finance.DOLLARS
import net.corda.finance.EUR
import net.corda.finance.contracts.*
import net.corda.testing.*
import net.corda.finance.contracts.AccrualAdjustment
import net.corda.finance.contracts.BusinessCalendar
import net.corda.finance.contracts.DateRollConvention
import net.corda.finance.contracts.DayCountBasisDay
import net.corda.finance.contracts.DayCountBasisYear
import net.corda.finance.contracts.Expression
import net.corda.finance.contracts.Fix
import net.corda.finance.contracts.FixOf
import net.corda.finance.contracts.Frequency
import net.corda.finance.contracts.PaymentRule
import net.corda.finance.contracts.Tenor
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.DUMMY_NOTARY_KEY
import net.corda.testing.DUMMY_PARTY
import net.corda.testing.LedgerDSL
import net.corda.testing.MEGA_CORP
import net.corda.testing.MEGA_CORP_KEY
import net.corda.testing.MEGA_CORP_PUBKEY
import net.corda.testing.MINI_CORP
import net.corda.testing.MINI_CORP_KEY
import net.corda.testing.ORACLE_PUBKEY
import net.corda.testing.TEST_TX_TIME
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.TestLedgerDSLInterpreter
import net.corda.testing.TestTransactionDSLInterpreter
import net.corda.testing.ledger
import net.corda.testing.node.MockServices
import net.corda.testing.transaction
import org.junit.Test
import java.math.BigDecimal
import java.time.LocalDate
@ -198,9 +223,9 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
}
class IRSTests : TestDependencyInjectionBase() {
private val megaCorpServices = MockServices(MEGA_CORP_KEY)
private val miniCorpServices = MockServices(MINI_CORP_KEY)
private val notaryServices = MockServices(DUMMY_NOTARY_KEY)
private val megaCorpServices = MockServices(listOf("net.corda.irs.contract"), MEGA_CORP_KEY)
private val miniCorpServices = MockServices(listOf("net.corda.irs.contract"), MINI_CORP_KEY)
private val notaryServices = MockServices(listOf("net.corda.irs.contract"), DUMMY_NOTARY_KEY)
@Test
fun ok() {
@ -297,7 +322,7 @@ class IRSTests : TestDependencyInjectionBase() {
*/
@Test
fun generateIRSandFixSome() {
val services = MockServices()
val services = MockServices(listOf("net.corda.irs.contract"))
var previousTXN = generateIRSTxn(1)
previousTXN.toLedgerTransaction(services).verify()
services.recordTransactions(previousTXN)
@ -370,6 +395,7 @@ class IRSTests : TestDependencyInjectionBase() {
return ledger(initialiseSerialization = false) {
transaction("Agreement") {
attachments(IRS_PROGRAM_ID)
output(IRS_PROGRAM_ID, "irs post agreement") { singleIRS() }
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timeWindow(TEST_TX_TIME)
@ -377,6 +403,7 @@ class IRSTests : TestDependencyInjectionBase() {
}
transaction("Fix") {
attachments(IRS_PROGRAM_ID)
input("irs post agreement")
val postAgreement = "irs post agreement".output<InterestRateSwap.State>()
output(IRS_PROGRAM_ID, "irs post first fixing") {
@ -400,6 +427,7 @@ class IRSTests : TestDependencyInjectionBase() {
fun `ensure failure occurs when there are inbound states for an agreement command`() {
val irs = singleIRS()
transaction(initialiseSerialization = false) {
attachments(IRS_PROGRAM_ID)
input(IRS_PROGRAM_ID, irs)
output(IRS_PROGRAM_ID, "irs post agreement", irs)
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
@ -413,6 +441,7 @@ class IRSTests : TestDependencyInjectionBase() {
val irs = singleIRS()
val emptySchedule = mutableMapOf<LocalDate, FixedRatePaymentEvent>()
transaction(initialiseSerialization = false) {
attachments(IRS_PROGRAM_ID)
output(IRS_PROGRAM_ID, irs.copy(calculation = irs.calculation.copy(fixedLegPaymentSchedule = emptySchedule)))
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timeWindow(TEST_TX_TIME)
@ -425,6 +454,7 @@ class IRSTests : TestDependencyInjectionBase() {
val irs = singleIRS()
val emptySchedule = mutableMapOf<LocalDate, FloatingRatePaymentEvent>()
transaction(initialiseSerialization = false) {
attachments(IRS_PROGRAM_ID)
output(IRS_PROGRAM_ID, irs.copy(calculation = irs.calculation.copy(floatingLegPaymentSchedule = emptySchedule)))
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timeWindow(TEST_TX_TIME)
@ -436,6 +466,7 @@ class IRSTests : TestDependencyInjectionBase() {
fun `ensure notionals are non zero`() {
val irs = singleIRS()
transaction(initialiseSerialization = false) {
attachments(IRS_PROGRAM_ID)
output(IRS_PROGRAM_ID, irs.copy(irs.fixedLeg.copy(notional = irs.fixedLeg.notional.copy(quantity = 0))))
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timeWindow(TEST_TX_TIME)
@ -443,6 +474,7 @@ class IRSTests : TestDependencyInjectionBase() {
}
transaction(initialiseSerialization = false) {
attachments(IRS_PROGRAM_ID)
output(IRS_PROGRAM_ID, irs.copy(irs.fixedLeg.copy(notional = irs.floatingLeg.notional.copy(quantity = 0))))
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timeWindow(TEST_TX_TIME)
@ -455,6 +487,7 @@ class IRSTests : TestDependencyInjectionBase() {
val irs = singleIRS()
val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(fixedRate = FixedRate(PercentageRatioUnit("-0.1"))))
transaction(initialiseSerialization = false) {
attachments(IRS_PROGRAM_ID)
output(IRS_PROGRAM_ID, modifiedIRS)
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timeWindow(TEST_TX_TIME)
@ -470,6 +503,7 @@ class IRSTests : TestDependencyInjectionBase() {
val irs = singleIRS()
val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.fixedLeg.notional.quantity, Currency.getInstance("JPY"))))
transaction(initialiseSerialization = false) {
attachments(IRS_PROGRAM_ID)
output(IRS_PROGRAM_ID, modifiedIRS)
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timeWindow(TEST_TX_TIME)
@ -482,6 +516,7 @@ class IRSTests : TestDependencyInjectionBase() {
val irs = singleIRS()
val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.floatingLeg.notional.quantity + 1, irs.floatingLeg.notional.token)))
transaction(initialiseSerialization = false) {
attachments(IRS_PROGRAM_ID)
output(IRS_PROGRAM_ID, modifiedIRS)
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timeWindow(TEST_TX_TIME)
@ -494,6 +529,7 @@ class IRSTests : TestDependencyInjectionBase() {
val irs = singleIRS()
val modifiedIRS1 = irs.copy(fixedLeg = irs.fixedLeg.copy(terminationDate = irs.fixedLeg.effectiveDate.minusDays(1)))
transaction(initialiseSerialization = false) {
attachments(IRS_PROGRAM_ID)
output(IRS_PROGRAM_ID, modifiedIRS1)
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timeWindow(TEST_TX_TIME)
@ -502,6 +538,7 @@ class IRSTests : TestDependencyInjectionBase() {
val modifiedIRS2 = irs.copy(floatingLeg = irs.floatingLeg.copy(terminationDate = irs.floatingLeg.effectiveDate.minusDays(1)))
transaction(initialiseSerialization = false) {
attachments(IRS_PROGRAM_ID)
output(IRS_PROGRAM_ID, modifiedIRS2)
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timeWindow(TEST_TX_TIME)
@ -515,6 +552,7 @@ class IRSTests : TestDependencyInjectionBase() {
val modifiedIRS3 = irs.copy(floatingLeg = irs.floatingLeg.copy(terminationDate = irs.fixedLeg.terminationDate.minusDays(1)))
transaction(initialiseSerialization = false) {
attachments(IRS_PROGRAM_ID)
output(IRS_PROGRAM_ID, modifiedIRS3)
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timeWindow(TEST_TX_TIME)
@ -524,6 +562,7 @@ class IRSTests : TestDependencyInjectionBase() {
val modifiedIRS4 = irs.copy(floatingLeg = irs.floatingLeg.copy(effectiveDate = irs.fixedLeg.effectiveDate.minusDays(1)))
transaction(initialiseSerialization = false) {
attachments(IRS_PROGRAM_ID)
output(IRS_PROGRAM_ID, modifiedIRS4)
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timeWindow(TEST_TX_TIME)
@ -538,6 +577,7 @@ class IRSTests : TestDependencyInjectionBase() {
val bd = BigDecimal("0.0063518")
transaction(initialiseSerialization = false) {
attachments(IRS_PROGRAM_ID)
output(IRS_PROGRAM_ID, "irs post agreement") { singleIRS() }
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timeWindow(TEST_TX_TIME)
@ -551,6 +591,7 @@ class IRSTests : TestDependencyInjectionBase() {
oldIRS.common)
transaction(initialiseSerialization = false) {
attachments(IRS_PROGRAM_ID)
input(IRS_PROGRAM_ID, oldIRS)
// Templated tweak for reference. A corrent fixing applied should be ok
@ -629,6 +670,7 @@ class IRSTests : TestDependencyInjectionBase() {
return ledger(initialiseSerialization = false) {
transaction("Agreement") {
attachments(IRS_PROGRAM_ID)
output(IRS_PROGRAM_ID, "irs post agreement1") {
irs.copy(
irs.fixedLeg,
@ -643,6 +685,7 @@ class IRSTests : TestDependencyInjectionBase() {
}
transaction("Agreement") {
attachments(IRS_PROGRAM_ID)
output(IRS_PROGRAM_ID, "irs post agreement2") {
irs.copy(
linearId = UniqueIdentifier("t2"),
@ -658,6 +701,7 @@ class IRSTests : TestDependencyInjectionBase() {
}
transaction("Fix") {
attachments(IRS_PROGRAM_ID)
input("irs post agreement1")
input("irs post agreement2")
val postAgreement1 = "irs post agreement1".output<InterestRateSwap.State>()

View File

@ -2,11 +2,25 @@ package net.corda.netmap.simulation
import net.corda.core.utilities.getOrThrow
import net.corda.testing.LogHelper
import net.corda.testing.setCordappPackages
import net.corda.testing.unsetCordappPackages
import org.junit.After
import org.junit.Before
import org.junit.Test
class IRSSimulationTest {
// TODO: These tests should be a lot more complete.
@Before
fun setup() {
setCordappPackages("net.corda.irs.contract")
}
@After
fun tearDown() {
unsetCordappPackages()
}
@Test fun `runs to completion`() {
LogHelper.setLevel("+messages") // FIXME: Don't manipulate static state in tests.
val sim = IRSSimulation(false, false, null)

Some files were not shown because too many files have changed in this diff Show More