From 532bbb5cca5844f5d9df9cae82b76cc5ec77d49a Mon Sep 17 00:00:00 2001 From: Clinton Date: Mon, 25 Sep 2017 17:05:18 +0100 Subject: [PATCH] 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. --- .../client/jackson/JacksonSupportTest.kt | 28 +- .../client/rpc/CordaRPCJavaClientTest.java | 6 +- .../corda/client/rpc/CordaRPCClientTest.kt | 4 + .../confidential/IdentitySyncFlowTests.kt | 2 + .../core/contracts/AttachmentConstraint.kt | 30 +- .../corda/core/contracts/TransactionState.kt | 45 +-- .../kotlin/net/corda/core/cordapp/Cordapp.kt | 33 ++ .../net/corda/core/cordapp/CordappContext.kt | 19 ++ .../net/corda/core/cordapp/CordappProvider.kt | 28 ++ .../corda/core/flows/ContractUpgradeFlow.kt | 18 ++ .../corda/core/internal/AbstractAttachment.kt | 2 +- .../core/internal/cordapp/CordappImpl.kt | 24 ++ .../kotlin/net/corda/core/node/ServiceHub.kt | 10 +- .../core/transactions/LedgerTransaction.kt | 29 +- .../core/transactions/SignedTransaction.kt | 5 + .../core/transactions/TransactionBuilder.kt | 47 ++- .../core/transactions/WireTransaction.kt | 27 +- .../core/contracts/DummyContractV2Tests.kt | 14 +- .../contracts/LedgerTransactionQueryTests.kt | 8 +- .../contracts/TransactionEncumbranceTests.kt | 11 + .../corda/core/contracts/TransactionTests.kt | 6 +- .../core/crypto/PartialMerkleTreeTest.kt | 2 + .../core/flows/CollectSignaturesFlowTests.kt | 8 +- .../core/flows/ContractUpgradeFlowTest.kt | 2 + .../net/corda/core/flows/FinalityFlowTests.kt | 4 + .../internal/ResolveTransactionsFlowTest.kt | 14 +- .../TransactionSerializationTests.kt | 6 +- .../corda/docs/IntegrationTestingTutorial.kt | 3 +- .../net/corda/docs/CustomVaultQueryTest.kt | 9 +- .../docs/FxTransactionBuildTutorialTest.kt | 7 +- .../WorkflowTransactionBuildTutorialTest.kt | 8 +- docs/source/oracles.rst | 2 +- .../corda/finance/contracts/universal/Cap.kt | 14 + .../finance/contracts/universal/Caplet.kt | 14 + .../contracts/universal/FXFwdTimeOption.kt | 14 + .../finance/contracts/universal/FXSwap.kt | 14 + .../corda/finance/contracts/universal/IRS.kt | 13 + .../contracts/universal/RollOutTests.kt | 13 + .../finance/contracts/universal/Swaption.kt | 14 + .../contracts/universal/ZeroCouponBond.kt | 14 + .../isolated/AnotherDummyContract.kt | 4 +- .../contracts/isolated/IsolatedDummyFlow.kt | 34 +++ .../{DummyPlugin.kt => IsolatedPlugin.kt} | 5 +- .../net.corda.core.node.CordaPluginRegistry | 2 +- .../finance/contracts/CommercialPaper.kt | 3 + .../corda/finance/flows/TwoPartyTradeFlow.kt | 2 +- .../contracts/asset/CashTestsJava.java | 14 +- .../finance/contracts/CommercialPaperTests.kt | 17 +- .../finance/contracts/asset/CashTests.kt | 47 ++- .../contracts/asset/ObligationTests.kt | 72 ++++- .../corda/finance/flows/CashExitFlowTests.kt | 7 +- .../corda/finance/flows/CashIssueFlowTests.kt | 5 +- .../finance/flows/CashPaymentFlowTests.kt | 5 +- .../serialization/DefaultKryoCustomizer.kt | 19 ++ .../nodeapi/internal/serialization/Kryo.kt | 2 +- .../serialization/SerializationScheme.kt | 2 +- .../corda/nodeapi/DummyContractBackdoor.kt | 17 ++ ...tachmentsClassLoaderStaticContractTests.kt | 74 +++++ .../AttachmentsClassLoaderTests.kt | 288 ++++++++---------- .../serialization/CordaClassResolverTests.kt | 6 +- .../net/corda/nodeapi/internal/isolated.jar | Bin 0 -> 12262 bytes .../resources/net/corda/nodeapi/isolated.jar | Bin 7977 -> 0 bytes .../node/services/AttachmentLoadingTests.kt | 108 +++++++ .../node/services/BFTNotaryServiceTests.kt | 6 +- .../node/services/DistributedServiceTests.kt | 4 +- .../node/services/RaftNotaryServiceTests.kt | 16 +- .../statemachine/LargeTransactionsTest.kt | 2 +- .../test/node/NodeStatePersistenceTests.kt | 2 +- .../net/corda/node/services/empty.jar | Bin 0 -> 22 bytes .../net/corda/node/services/isolated.jar | Bin 0 -> 12477 bytes .../net/corda/node/internal/AbstractNode.kt | 26 +- .../corda/node/internal/cordapp/Cordapp.kt | 26 -- .../node/internal/cordapp/CordappLoader.kt | 94 ++++-- .../node/internal/cordapp/CordappProvider.kt | 38 --- .../internal/cordapp/CordappProviderImpl.kt | 79 +++++ .../corda/node/services/CoreFlowHandlers.kt | 2 +- .../persistence/NodeAttachmentService.kt | 15 +- .../services/vault/VaultQueryJavaTests.java | 4 +- .../net/corda/node/CordaRPCOpsImplTest.kt | 8 +- .../corda/node/cordapp/CordappLoaderTest.kt | 9 +- .../node/cordapp/CordappProviderImplTests.kt | 72 +++++ .../node/cordapp/CordappProviderTests.kt | 35 --- .../node/messaging/TwoPartyTradeFlowTests.kt | 14 +- .../corda/node/services/NotaryChangeTests.kt | 11 +- .../events/NodeSchedulerServiceTest.kt | 25 +- .../services/events/ScheduledFlowTests.kt | 15 +- .../persistence/HibernateConfigurationTest.kt | 2 + .../persistence/NodeAttachmentStorageTest.kt | 2 + .../statemachine/FlowFrameworkTests.kt | 5 +- .../transactions/NotaryServiceTests.kt | 6 +- .../ValidatingNotaryServiceTests.kt | 6 +- .../services/vault/NodeVaultServiceTest.kt | 8 +- .../node/services/vault/VaultQueryTests.kt | 25 +- .../node/services/vault/VaultWithCashTest.kt | 6 +- .../net/corda/node/cordapp/isolated.jar | Bin 9023 -> 12182 bytes .../corda/attachmentdemo/AttachmentDemo.kt | 4 +- .../net/corda/irs/flows/RatesFixFlow.kt | 4 +- .../corda/irs/api/NodeInterestRatesTest.kt | 18 +- .../kotlin/net/corda/irs/contract/IRSTests.kt | 56 +++- .../netmap/simulation/IRSSimulationTest.kt | 14 + .../net/corda/vega/SimmValuationTest.kt | 14 + .../net/corda/traderdemo/TraderDemoTest.kt | 15 + .../traderdemo/TransactionGraphSearchTests.kt | 4 +- .../node/testing/MockServiceHubInternal.kt | 8 +- .../kotlin/net/corda/testing/NodeTestUtils.kt | 1 + .../kotlin/net/corda/testing/driver/Driver.kt | 7 +- .../net/corda/testing/node/MockServices.kt | 22 +- .../kotlin/net/corda/testing/CoreTestUtils.kt | 16 + .../main/kotlin/net/corda/testing/TestDSL.kt | 19 +- .../testing/TransactionDSLInterpreter.kt | 44 ++- .../corda/testing/contracts/DummyContract.kt | 1 + .../testing/contracts/DummyContractV2.kt | 8 +- .../testing/node/MockAttachmentStorage.kt | 7 +- .../corda/testing/node/MockCordappProvider.kt | 39 +++ .../net/corda/verifier/GeneratedLedger.kt | 23 +- .../net/corda/verifier/VerifierTests.kt | 9 +- 116 files changed, 1601 insertions(+), 599 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt create mode 100644 core/src/main/kotlin/net/corda/core/cordapp/CordappContext.kt create mode 100644 core/src/main/kotlin/net/corda/core/cordapp/CordappProvider.kt create mode 100644 core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt create mode 100644 finance/isolated/src/main/kotlin/net/corda/finance/contracts/isolated/IsolatedDummyFlow.kt rename finance/isolated/src/main/kotlin/net/corda/finance/contracts/isolated/{DummyPlugin.kt => IsolatedPlugin.kt} (50%) create mode 100644 node-api/src/test/kotlin/net/corda/nodeapi/DummyContractBackdoor.kt create mode 100644 node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt rename node-api/src/test/kotlin/net/corda/nodeapi/{ => internal}/AttachmentsClassLoaderTests.kt (57%) create mode 100644 node-api/src/test/resources/net/corda/nodeapi/internal/isolated.jar delete mode 100644 node-api/src/test/resources/net/corda/nodeapi/isolated.jar create mode 100644 node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt create mode 100644 node/src/integration-test/resources/net/corda/node/services/empty.jar create mode 100644 node/src/integration-test/resources/net/corda/node/services/isolated.jar delete mode 100644 node/src/main/kotlin/net/corda/node/internal/cordapp/Cordapp.kt delete mode 100644 node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProvider.kt create mode 100644 node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt create mode 100644 node/src/test/kotlin/net/corda/node/cordapp/CordappProviderImplTests.kt delete mode 100644 node/src/test/kotlin/net/corda/node/cordapp/CordappProviderTests.kt create mode 100644 testing/test-utils/src/main/kotlin/net/corda/testing/node/MockCordappProvider.kt diff --git a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt index fec036aeb2..49d6083d50 100644 --- a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt +++ b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt @@ -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, diff --git a/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java b/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java index 3842299920..62dc023e28 100644 --- a/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java +++ b/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java @@ -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 services = new HashSet<>(singletonList(new ServiceInfo(ValidatingNotaryService.Companion.getType(), null))); CordaFuture> 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 diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt index 96322bf7b8..b4c10fa808 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt @@ -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 diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt index 6358fe92db..f2907c7c7c 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt @@ -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 diff --git a/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt b/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt index 0b09f46559..2f0135ddd1 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt @@ -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): 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) = 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") + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/contracts/TransactionState.kt b/core/src/main/kotlin/net/corda/core/contracts/TransactionState.kt index 310e1030b2..5bf40afc60 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/TransactionState.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/TransactionState.kt @@ -15,43 +15,16 @@ data class TransactionState @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 @JvmOverloads constructor( /** * A validator for the contract attachments on the transaction. */ - val constraint: AttachmentConstraint = AlwaysAcceptAttachmentConstraint) + val constraint: AttachmentConstraint = AutomaticHashConstraint) // DOCEND 1 diff --git a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt new file mode 100644 index 0000000000..ff9426ec90 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt @@ -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 + val initiatedFlows: List>> + val rpcFlows: List>> + val services: List> + val plugins: List + val customSchemas: Set + val jarPath: URL + val cordappClasses: List +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/cordapp/CordappContext.kt b/core/src/main/kotlin/net/corda/core/cordapp/CordappContext.kt new file mode 100644 index 0000000000..3c7be4a3e2 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/cordapp/CordappContext.kt @@ -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) diff --git a/core/src/main/kotlin/net/corda/core/cordapp/CordappProvider.kt b/core/src/main/kotlin/net/corda/core/cordapp/CordappProvider.kt new file mode 100644 index 0000000000..289e606ac4 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/cordapp/CordappProvider.kt @@ -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? +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt b/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt index f5813fdb05..0612d5d9aa 100644 --- a/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt @@ -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> ) : AbstractStateReplacementFlow.Instigator>>(originalState, newContractClass) { + companion object { + fun assembleBareTx( + stateRef: StateAndRef, + upgradedContractClass: Class>, + 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()) diff --git a/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt b/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt index d934fefd46..07c82039df 100644 --- a/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt +++ b/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt @@ -24,7 +24,6 @@ abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment { /** @see */ 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? = null openAsJAR().use { jar -> + val shredder = ByteArray(1024) while (true) { val entry = jar.nextJarEntry ?: break if (entry.isDirectory || unsignableEntryName.matches(entry.name)) continue diff --git a/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt new file mode 100644 index 0000000000..4f7aecd6fe --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt @@ -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, + override val initiatedFlows: List>>, + override val rpcFlows: List>>, + override val services: List>, + override val plugins: List, + override val customSchemas: Set, + 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) +} diff --git a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt index 650f3bb486..9bfebc24db 100644 --- a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt +++ b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt @@ -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 toStateAndRef(stateRef: StateRef): StateAndRef { val stx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash) return if (stx.isNotaryChangeTransaction()) { - stx.resolveNotaryChangeTransaction(this).outRef(stateRef.index) + stx.resolveNotaryChangeTransaction(this).outRef(stateRef.index) } else { - stx.tx.outRef(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) } /** diff --git a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt index aa762d422d..4fa5778071 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt @@ -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() + (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) } } diff --git a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt index 7da67105f1..31857af7e9 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt @@ -155,6 +155,11 @@ data class SignedTransaction(val txBits: SerializedBytes, } } + /** + * 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() diff --git a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt index bcc2715f78..9f972ee155 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt @@ -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)) } -} \ No newline at end of file +} diff --git a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt index 95f18b185b..39de4816a9 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -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, 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, val privacySalt: Pr fun toLedgerTransaction( resolveIdentity: (PublicKey) -> Party?, resolveAttachment: (SecureHash) -> Attachment?, - resolveStateRef: (StateRef) -> TransactionState<*>? + resolveStateRef: (StateRef) -> TransactionState<*>?, + resolveContractAttachment: (TransactionState) -> 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, val privacySalt: Pr return buf.toString() } + private fun findAttachmentContracts(resolvedInputs: List>, + resolveContractAttachment: (TransactionState) -> AttachmentId?, + resolveAttachment: (SecureHash) -> Attachment? + ): List { + 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) diff --git a/core/src/test/kotlin/net/corda/core/contracts/DummyContractV2Tests.kt b/core/src/test/kotlin/net/corda/core/contracts/DummyContractV2Tests.kt index e8421fc7d0..35d32dee7f 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/DummyContractV2Tests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/DummyContractV2Tests.kt @@ -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) diff --git a/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt b/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt index 12238a6992..5f32d94678 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt @@ -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 { diff --git a/core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt b/core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt index 65eac9056c..27eaebf45c 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt @@ -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 } diff --git a/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt b/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt index 3d965aea46..3122539a76 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt @@ -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>() val outputs = listOf(baseOutState, baseOutState.copy(notary = ALICE), baseOutState.copy(notary = BOB)) val commands = emptyList>() - val attachments = emptyList() + val attachments = listOf(ContractAttachment(MockAttachment(), DUMMY_PROGRAM_ID)) val id = SecureHash.randomSHA256() val timeWindow: TimeWindow? = null val privacySalt: PrivacySalt = PrivacySalt() diff --git a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt index ce67219b0b..eeec2185fa 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt @@ -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().copy(owner = MINI_CORP)) command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() } diff --git a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt index 34a7bf5887..c61e9ce64a 100644 --- a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt @@ -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 lateinit var c: StartedNode 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>) { diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt index f749e6e262..8ec897b622 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -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 diff --git a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt index 929a65fb90..865026c958 100644 --- a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt @@ -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 diff --git a/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt b/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt index 5a3594b6de..c5762a136e 100644 --- a/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt @@ -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 lateinit var b: StartedNode 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 diff --git a/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt b/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt index 244db2eddc..88ef89b662 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt @@ -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 diff --git a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt index 0a107b6c12..e4de7b4864 100644 --- a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt +++ b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt @@ -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(), startFlowPermission() diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt index 19feeb4737..c8a8c89fc6 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt @@ -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) } -} \ No newline at end of file +} diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt index 7b04baf0d0..15c16c32b1 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt @@ -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 diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt index 48c3a0b0c8..d625c1483e 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt @@ -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) } -} \ No newline at end of file +} diff --git a/docs/source/oracles.rst b/docs/source/oracles.rst index d6c9f101d6..c8c6de060a 100644 --- a/docs/source/oracles.rst +++ b/docs/source/oracles.rst @@ -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. diff --git a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt index 11e67b0ef4..95c7e983fd 100644 --- a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt +++ b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt @@ -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 { diff --git a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Caplet.kt b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Caplet.kt index 8f6c2c61dc..8793dd65b9 100644 --- a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Caplet.kt +++ b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Caplet.kt @@ -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 { diff --git a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/FXFwdTimeOption.kt b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/FXFwdTimeOption.kt index 31bd7fbc1b..ec78fc1b0c 100644 --- a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/FXFwdTimeOption.kt +++ b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/FXFwdTimeOption.kt @@ -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 { diff --git a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/FXSwap.kt b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/FXSwap.kt index b4abe74eef..26189d2424 100644 --- a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/FXSwap.kt +++ b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/FXSwap.kt @@ -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`() { diff --git a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/IRS.kt b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/IRS.kt index 8983e60e93..ae55271902 100644 --- a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/IRS.kt +++ b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/IRS.kt @@ -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() { diff --git a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/RollOutTests.kt b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/RollOutTests.kt index 03d8a14eea..40d4b7bb94 100644 --- a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/RollOutTests.kt +++ b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/RollOutTests.kt @@ -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`() { diff --git a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Swaption.kt b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Swaption.kt index 34dcdc6f64..590e7a47ac 100644 --- a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Swaption.kt +++ b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Swaption.kt @@ -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 { diff --git a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/ZeroCouponBond.kt b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/ZeroCouponBond.kt index fdce06b22b..3d79d0df1b 100644 --- a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/ZeroCouponBond.kt +++ b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/ZeroCouponBond.kt @@ -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()) diff --git a/finance/isolated/src/main/kotlin/net/corda/finance/contracts/isolated/AnotherDummyContract.kt b/finance/isolated/src/main/kotlin/net/corda/finance/contracts/isolated/AnotherDummyContract.kt index b9b711af80..d6e85290b8 100644 --- a/finance/isolated/src/main/kotlin/net/corda/finance/contracts/isolated/AnotherDummyContract.kt +++ b/finance/isolated/src/main/kotlin/net/corda/finance/contracts/isolated/AnotherDummyContract.kt @@ -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 - } \ No newline at end of file diff --git a/finance/isolated/src/main/kotlin/net/corda/finance/contracts/isolated/IsolatedDummyFlow.kt b/finance/isolated/src/main/kotlin/net/corda/finance/contracts/isolated/IsolatedDummyFlow.kt new file mode 100644 index 0000000000..855c06cea4 --- /dev/null +++ b/finance/isolated/src/main/kotlin/net/corda/finance/contracts/isolated/IsolatedDummyFlow.kt @@ -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() { + @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() { + @Suspendable + override fun call() { + val stx = subFlow(ReceiveTransactionFlow(session, checkSufficientSignatures = false)) + stx.verify(serviceHub) + } + } +} \ No newline at end of file diff --git a/finance/isolated/src/main/kotlin/net/corda/finance/contracts/isolated/DummyPlugin.kt b/finance/isolated/src/main/kotlin/net/corda/finance/contracts/isolated/IsolatedPlugin.kt similarity index 50% rename from finance/isolated/src/main/kotlin/net/corda/finance/contracts/isolated/DummyPlugin.kt rename to finance/isolated/src/main/kotlin/net/corda/finance/contracts/isolated/IsolatedPlugin.kt index acabde2ea4..61e0288d88 100644 --- a/finance/isolated/src/main/kotlin/net/corda/finance/contracts/isolated/DummyPlugin.kt +++ b/finance/isolated/src/main/kotlin/net/corda/finance/contracts/isolated/IsolatedPlugin.kt @@ -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() \ No newline at end of file +class IsolatedPlugin : CordaPluginRegistry() \ No newline at end of file diff --git a/finance/isolated/src/main/resources/META-INF/services/net.corda.core.node.CordaPluginRegistry b/finance/isolated/src/main/resources/META-INF/services/net.corda.core.node.CordaPluginRegistry index a1b82d14a0..759dc08195 100644 --- a/finance/isolated/src/main/resources/META-INF/services/net.corda.core.node.CordaPluginRegistry +++ b/finance/isolated/src/main/resources/META-INF/services/net.corda.core.node.CordaPluginRegistry @@ -1 +1 @@ -net.corda.finance.contracts.isolated.DummyPlugin \ No newline at end of file +net.corda.finance.contracts.isolated.IsolatedPlugin \ No newline at end of file diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/CommercialPaper.kt b/finance/src/main/kotlin/net/corda/finance/contracts/CommercialPaper.kt index 68d2bc62bc..592456a84f 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/CommercialPaper.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/CommercialPaper.kt @@ -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 { diff --git a/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt index b69fad717a..d8521f19ed 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt @@ -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. diff --git a/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java b/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java index f8d510868c..be92224604 100644 --- a/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java +++ b/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java @@ -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(); }); diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt b/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt index f5b5d31fd4..0570b327c5 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt @@ -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 diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt index a3068b3606..033c3bb8b1 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt @@ -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().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, 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, 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().copy(owner = AnonymousParty(ALICE_PUBKEY))) + output(CASH_PROGRAM_ID, "MEGA_CORP cash 2", "MEGA_CORP cash".output().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().copy(owner = ALICE)) + output(CASH_PROGRAM_ID, "MEGA_CORP cash 3", "MEGA_CORP cash".output().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()) diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt index 85a4b64a7a..de3e6cb9e3 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt @@ -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 ) = 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().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().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().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().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().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().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().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().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 = (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 } diff --git a/finance/src/test/kotlin/net/corda/finance/flows/CashExitFlowTests.kt b/finance/src/test/kotlin/net/corda/finance/flows/CashExitFlowTests.kt index ecfb07035a..1180a73ff0 100644 --- a/finance/src/test/kotlin/net/corda/finance/flows/CashExitFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/flows/CashExitFlowTests.kt @@ -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 @@ -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 diff --git a/finance/src/test/kotlin/net/corda/finance/flows/CashIssueFlowTests.kt b/finance/src/test/kotlin/net/corda/finance/flows/CashIssueFlowTests.kt index 1102d2aeab..b5901c1c30 100644 --- a/finance/src/test/kotlin/net/corda/finance/flows/CashIssueFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/flows/CashIssueFlowTests.kt @@ -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 private lateinit var bankOfCorda: Party private lateinit var notaryNode: StartedNode @@ -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] diff --git a/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt b/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt index 73d5330b12..5fe62e9d3c 100644 --- a/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt @@ -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 @@ -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] diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/DefaultKryoCustomizer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/DefaultKryoCustomizer.kt index 13d0d08f5b..5a9b6c537b 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/DefaultKryoCustomizer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/DefaultKryoCustomizer.kt @@ -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() { + 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 { + val attachment = GeneratedAttachment(input.readBytesWithLength()) + return ContractAttachment(attachment, input.readString()) + } + } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/Kryo.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/Kryo.kt index b8e105117d..06f73a4a95 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/Kryo.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/Kryo.kt @@ -616,4 +616,4 @@ class ThrowableSerializer(kryo: Kryo, type: Class) : Serializer } private fun Throwable.setSuppressedToSentinel() = suppressedField.set(this, sentinelValue) -} \ No newline at end of file +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt index 6e31cb3c9a..c4906ddea0 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt @@ -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. diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/DummyContractBackdoor.kt b/node-api/src/test/kotlin/net/corda/nodeapi/DummyContractBackdoor.kt new file mode 100644 index 0000000000..5d7e069c6f --- /dev/null +++ b/node-api/src/test/kotlin/net/corda/nodeapi/DummyContractBackdoor.kt @@ -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 +} diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt new file mode 100644 index 0000000000..77733ec060 --- /dev/null +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt @@ -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 + 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) + } +} \ No newline at end of file diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/AttachmentsClassLoaderTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt similarity index 57% rename from node-api/src/test/kotlin/net/corda/nodeapi/AttachmentsClassLoaderTests.kt rename to node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt index a900d72948..5fc205e666 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/AttachmentsClassLoaderTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt @@ -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() 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 - 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("magicString").value) + assertEquals("helloworld", contract.declaredField("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("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("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()) + } } } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt index ce11cb7dff..36641f911e 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt @@ -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() { class CordaClassResolverTests { val factory: SerializationFactory = object : SerializationFactory() { override fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + TODO("not implemented") } override fun serialize(obj: T, context: SerializationContext): SerializedBytes { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + TODO("not implemented") } } diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/isolated.jar b/node-api/src/test/resources/net/corda/nodeapi/internal/isolated.jar new file mode 100644 index 0000000000000000000000000000000000000000..17bf0c2436c9aee598228a41c675b4421a7cfa37 GIT binary patch literal 12262 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 WD#=1Y-}Aw^``NiW!XGK`zx@xEsB`}S literal 0 HcmV?d00001 diff --git a/node-api/src/test/resources/net/corda/nodeapi/isolated.jar b/node-api/src/test/resources/net/corda/nodeapi/isolated.jar deleted file mode 100644 index ce003bd76d5502c4c8f2e6dabd000cb10ecb532a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7977 zcmb`McUV)~((sYqL`o>3_mWTyAVoT%NRPCDAQD38p*I0RsuU@PCcO(t6GVD%N^c@9 zfS?EnDn&(+^5S=|_sI9?x#vCS-FfyONuK%5%wDr*_L_ld;o$>th>3}DaBw(I?>{aQ zobxz3s)kBJ8oFvCMusFf1V8@6!+~iLeHXyN{r69>-%zf=rjWaCO{6yR`iSh}P;Xc-a&ab1Km_7h z3(8|JzMqMBWhcs73^T@TI4Zo|a}~LwB>&wjDvC0)JEvYfIlX`H75C3x*?YJ+ zAv{smH_!-sXJJPdPbYil8)z46FDKL)FOim(mNut%n3cD%jBs+xbL!UDhxth=t+hif zJuR;d*+r@f`5}#kCL)ie6!@ySFR5M`f{3(pb3J)hBraJ$0R(-xj{E-}y)>PPq}q1_ z|I|qQQzM;Go_~MsNdFB$y0}{-{yv8K493RZ8R3jX{e2wsFL6j0XHRzo($nMbgL(c7 z_{Tc`eGPhlRfCeVi>DpRUBwHHz6t%eXWUR1G#cS-?ZFLoN1eu-FwzO(;c+IyD)e<+ zCKV}fA^F@K^wSBIY1+unDTOm$Yhb6;-y|03x{YrnEN#f-e-&Om{j6DOprSpT2CjRB z88*eGKnl^NNyekktyz25GCP}=m3FkVH}8WJ@0L0?J>gv-7_YkDI-#tNNvms&EZ+bn z@^lR}G#7~Zv+pf(>lAkeG}BnygFKxsD+$jI;!&|?Mj#L6UMzkL4Jb(5I5gBVS?=W7 zIzAzgO(IFQ)dYXbe7V@HP|^vVQYX<0!^puRwnb$hYvxcJce~lp6g5{B(n6-YB>0Zj z-OWmI;y3KnuHJ^`LNZo_y86Zu3qIFIN)5AKj4#*iC75I`?Cumt8~DU4z%_fL&|NlP zB(JY#d>qL&rg+@4jnxaEz^)|jlIR^vx7;ZUd&^0Lc&DeEfgD7V)!a9@)FnvWulQQ? zE_EXu2f<@_tu4EdD{WNhxY-G#z8em@Qc?9}C{%@kOIrQgI~B9!fnd?-EFbY=fH7*$ zl8i!uy0ZK}Q_!IUgNvxY_$$SmufT^Qy~NccCNhu}X2%X8x*VU|yz?2dZ|B8Enbx=G zEQgoJ;A1u|5u3?8ScHE^+aU9+LrP~h@ZB|2}ko_XMiu7glaFr;J-@quSZhXPLA>K1#U{xRi z>IuU~=0}|mH+@fcyp-lLx7-z?VPfZrWCFd*(gNdtW~It3$yvE^hVY`vAL-S5Xw$#PzI^1H^ zHhoyhd$4raNf65_RecO?S7*+KIe<6~!iV@&oSRk2V_mgku`(z|>#oq)^g^^WqKl#5 zw0AJO4uqK=C^6>O6Hg>AvqDeE-qgOW9z4D%5s}e-S>o%+<0k_(VVs+sFD49TtWF?L z_u>^FL>aOeu<^xgKnA8Dp{5d$X?)-iGB1iGw52n@6VtgX^L^Rn+vOj5o-X_1X|?@* zNmBhs%WmNLn~Y+#qz8Zlj^A%Rl4%ya*v!sp8KdxUFrOkHZM03KTv0}L?U7+oMQgK+ zFy3Ra`Lt&47IFEneD^H9 zn#?}7`$N#lF8KB&Kg8Tq8@y1>{h zEwb*yDCwNmZOD2|KpK;T&%?ghiF{K!k{*UQC#A_I79Sc;Wjn-rN%{)!ewb0+TP&44 zR6^2(+>)uQCN_UH=4u2~b-Ii(<^lgrtb0JHOcDcqiq>u|_yJR$js>1pnrW)fl(h55ytT#lpIG%p#k#LflV=TO4*R#M_RF2Tt(vC01 zU_Q1QYsnEyUZcT3(@n|tm|4p_SKK|kv(Yk0IJR{rH6+?1!k|$(kD=Pkyb_qgr9HY_ z2eDsVJ8uS~XxnB)E&CZ@%|u(rFShZie)(4PT%@QTsE?{Q_F3wWz7Zth&MT zgO&F!aK8c~Uawl?&0y=y6sne`u7j$kb&9)OW$T%I_f4sUi)<}TlqU&34QAZTdL^*o zo8IIE>-M*QeUbmXZR8B@K0Plsx<6qSmmjudhX-}Cx?W@0p8!e{VwCNL?v*O59Qz(yautdPefnGE3FbWkCsuMYw<6utHAQHNb9<-a ztKLa?2@5@zv~e$8!{eSdTBWE(ym#{|sMNH6Ie8eIVmLwY6w*qP@vTjx?HlKIChZcv@ne(Evd)v~6zKKax=S)2V~A47-xJF3 zeG#wW!E@wmx{~6n7B-DJAz3Js!7=kb=RKxrklkXvO>hAEsIxjfp8DY%a6vDfcwt#L zjGag0PHWhwG}+K>>0GUuV;kKXQbqsvgDFY7sMytSFLsQHTY;>_+oPdAqqXeCx30Rv zVTy`_PYvthg84c(fXsIK^0kSg{sgOcGiR?3>rKEh8uFp;>JN4sfPflp2~%FK6tkf^ zxP{2ei;GdwKHOGmBqk1`K+dL!0uDcw%1=cZNcWd&;_d(uvG+j{c`}<)DRR{jR8ch% zK94^nwnRLR4=rRE##Y+gG%;k|4Tz2@yZ4mmt~x861g<-29=Pt$%zSWuKypUmBlAEK zxwKll%RN}oZN`G;t8ir5u;+Z)w;donL=_g&CAJxL{z{#i0t_$SHySEbw7)PPBy@>q=; zV-iLwN_R((TmtuQc$CM?E>DL<4S^If4Y-FFY)?s6cHP#NfcFkm_#{D=$LQvjht#g& zn$f!kAO%qJ;uHb@8_i_#*2&4qr;>7VNBauxCnPUPEkw4na zmsbpWuwg2!V8AIONIsZWR9?x%>dU~m>#p0+tpgo*C+}35a=k~7N}y|a_!iuhXVYqD zxvsgcCS3DuxRwK)9{G$hoFgMzcZsG+`YOz5ZgtenBcSM1I-9VU$-R;fkYQ1js{VGE|0HjJr1KyMtbO3XDE ztIr1jh4~#)m|p8=wJ8fHCpzRO=$VPtC$siFq|dVOgkUf4?s)SF3^x$bUs>}C&u`Pv zpoue6j2H*!5#1tBrC57HL=PO9!l`E>oA2Qv!Hc~3v5g8fucX}Q$0j0XKC?fUvf>wm zIeGR{mL2ILmYM5FpK(8vwOQ;Q`wHLO_6NVcQrS`I4vk)7yFXNXj{DxVd#GxC0(zXG2c%HXqQ zQr?G`@Sf0Satqw&7I_{Qrm<0)**z0o#s)+5Y<-let{3)!BW!jtL?nc4v+3sJ#IJ}l zx3c)*+MQwq-6MU<*NjAKuh8*c6hJUv07mS~4r+cGmjr>WqAfgCt+ zjJW0JRgW2Jb*SF(ioem$%+s18$s$}O^YZ3VX!Am}?K20t&*5<$v)MWR-iq905uM!% zo{R1}`2oVK8c1o>+U|4O@uI^Mf>AsWZp8_YyL~@*jCjFdVVYRYHTDa-+O+(Md(STj zf|WJOK2jPniuv)Y6O)v6Pm0xC=3 zC?F#R&Q#+=<3xpoJ;IYXO`Nv&A(|z65O|k+Ls(I}!WRg^Y&LNaiTc-pJbCBiq~LfV$b%;oB>NX4DB&h<-da#=F=kGnYak8TT% zv)}qO78IVoqZcLIyt$KwPUBEd5FY9R-myBNt2WFob$gvR&gU6o{1Pairh2CXat^9U z+=Ks?K>J*~Vqym{I7G3yqr5{pV~J;5*jE0qgU4+K~_@c;yRuZ?bHWJ&JX1Rfpl&~!ktD5(_X zSzXG{jYlKN=9fbf3^{xZt(&jDbmKYjHYI%9N?ca`A*e(<=f*?TixGzj@s01?q#x<| zL6>2Mwlji~dLtIHXs@y=rr7!gctzq`g?oWnnJD0G>RkicCU@7N?MQ2rQgTO7_W`&& zz5h`q24Q&*(asu%=g)5_^1AY_ldwP{STfgN3^qKoSJMAkvE*bE#L7GZHtWQ#(HeWy zWhv_|3vYT$5*p|_V`EYkkE_bj%mMh|(OY`wO}y8fkQO<~q@RF_l{le9KI|lNd9kt}{HgAN@6E596KFnif#5D~~$w2S$+#Rq4swijl0HE69H~Y&`zSUN<7POu{M_ zfTE5Dq&^&OH5uEgFab@|Uy}Cbs$YxRU5#cCP0rQRaP)#l+idNt`1T3h>6AUcGDbCu z#dp$itX%`Wj5JQtc%vpT%aFHVZN<=_DzCLu1aRr>5&b$J=FOiTkmmC2mPVt`gT~zQ zpqqro)oS};Ph?OivOb^JZ6iD4TSZyURyhIT`8V}z7lSRJ9CFjJ%)HG@*bX%-2^mYO zez4agN*-I85FkRjYlQ844C0!&Wq5o$el-Zk8LjSlR1)?HFDncSzYI9M zav;*|hV{=&*73ERzBxwnM0eMce33)pO<<4F{E|IObjO=}h?3fHYLfc=yuxHzt1q>+ zUTRwU2@GSvlk<8`E;`}ORyVkn3Gx>k)&-$?<+ZKBF1Nm}$Uy|4*81RA?Q#Y$nng)f z^=+SR#3xaA`wq>E&b;rdF!V^Slih#}xoi}^VZ{S!Hl=1ydKtGWPb#M4x9pCtXICvDmvrNtr-KCnkbRm9cZ zGdy-g+=#J0PDR`gz+e@4=1vvb&h> zfhox%!PvNx_uEkIvJTkMrd^J=AX?$mE5$qFbgX1x>bcmo1>s@PgJFdfwK~pf?>Y@~ zGJ2Pw@Z&ee*vSQM$|`FWk95YqmT#e>MVIgCG*j?tg@$Xr%i*iYRLag(w7olm%aWRY zl{?@{F$M@?VDI(2&t54m@${nf$AW5#L+0lPyXWXgmJ&Y^wt7E~(#ESlSV9}B&l%!P z-p{*%wGRPeds}%cfl1^x=h19$$ae`*p~_35HZaIGIq&&OChQ5x_ubGIan}}px*P7| zf6vJ0Z~-`H$1^`hE+jZ4r``Xbub+lB1pgY={QvSl4>tg3;#a(|P5L6%D6H=6`O*k4*j>^*^7@RnMreBKjxl&oozmqx + 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("xxx") { + proxy.startFlowDynamic(clazz, party).returnValue.getOrThrow() + } + } + } finally { + bankA.stop() + bankB.stop() + } + } + } +} diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index ad36386ed9..42456f1aea 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -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) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt index 36db192797..69915138dd 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt @@ -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> - 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( diff --git a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt index d66d083241..22a327cf3d 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt @@ -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( diff --git a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt index 97d4d83503..9163064c86 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt @@ -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) diff --git a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt index 45e3975c43..efd07f1a55 100644 --- a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt @@ -145,7 +145,7 @@ class SendMessageFlow(private val message: Message) : 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 Any?>() protected lateinit var database: CordaPersistence protected var dbCloser: (() -> Any?)? = null - lateinit var cordappProvider: CordappProvider + lateinit var cordappProvider: CordappProviderImpl protected val _nodeReadyFuture = openFuture() /** 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 { 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 cordaService(type: Class): 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() } diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/Cordapp.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/Cordapp.kt deleted file mode 100644 index b07acb7aa0..0000000000 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/Cordapp.kt +++ /dev/null @@ -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, - val initiatedFlows: List>>, - val rpcFlows: List>>, - val services: List>, - val plugins: List, - val customSchemas: Set, - val jarPath: URL) \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt index 95d8ecc27e..f29fe98ed4 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt @@ -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) * for classpath scanning. */ fun createDefault(baseDir: Path): CordappLoader { - val pluginsDir = baseDir / "plugins" + val pluginsDir = getPluginsPath(baseDir) return CordappLoader(if (!pluginsDir.exists()) emptyList() 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 = 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) * @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection */ @VisibleForTesting - internal fun createDevMode(scanJars: List) = CordappLoader(scanJars) + fun createDevMode(scanJars: List) = CordappLoader(scanJars) + + private fun createScanPackage(scanPackage: String): List { + 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 = emptyList() + private val generatedCordapps = mutableMapOf() } private fun loadCordapps(): List { 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) } private fun findContractClassNames(scanResult: ScanResult): List { - return scanResult.getNamesOfClassesImplementing(Contract::class.java) + return (scanResult.getNamesOfClassesImplementing(Contract::class.java) + scanResult.getNamesOfClassesImplementing(UpgradedContract::class.java)).distinct() } private fun findPlugins(cordappJarPath: URL): List { diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProvider.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProvider.kt deleted file mode 100644 index ea182bc75c..0000000000 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProvider.kt +++ /dev/null @@ -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 - - /** - * 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 { - val cordappsWithAttachments = cordapps.filter { !it.contractClassNames.isEmpty() } - val attachmentIds = cordappsWithAttachments.map { it.jarPath.openStream().use { attachmentStorage.importAttachment(it) } } - return attachmentIds.zip(cordappsWithAttachments).toMap() - } -} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt new file mode 100644 index 0000000000..3eb485787f --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt @@ -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 + + /** + * 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 { + 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) } +} diff --git a/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt b/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt index 6c2ad845a3..e3819b661d 100644 --- a/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt +++ b/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt @@ -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) diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt index 94c00f52ca..b6acddd45e 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt @@ -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(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 } diff --git a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java index b9df7dccd4..f963189d1f 100644 --- a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java +++ b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java @@ -62,13 +62,14 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase { @Before public void setUp() { + setCordappPackages("net.corda.testing.contracts", "net.corda.finance.contracts.asset"); ArrayList keys = new ArrayList<>(); keys.add(getMEGA_CORP_KEY()); keys.add(getDUMMY_NOTARY_KEY()); Set requiredSchemas = Collections.singleton(CashSchemaV1.INSTANCE); IdentityService identitySvc = makeTestIdentityService(); @SuppressWarnings("unchecked") - Pair databaseAndServices = makeTestDatabaseAndMockServices(requiredSchemas, keys, () -> identitySvc); + Pair 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(); } /** diff --git a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt index eb05bac499..f713bf0f5e 100644 --- a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt @@ -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 diff --git a/node/src/test/kotlin/net/corda/node/cordapp/CordappLoaderTest.kt b/node/src/test/kotlin/net/corda/node/cordapp/CordappLoaderTest.kt index a4cf1bfb85..aadf3ee066 100644 --- a/node/src/test/kotlin/net/corda/node/cordapp/CordappLoaderTest.kt +++ b/node/src/test/kotlin/net/corda/node/cordapp/CordappLoaderTest.kt @@ -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() { } @InitiatedBy(DummyFlow::class) -class LoaderTestFlow(unusedSession: FlowSession) : FlowLogic() { +class LoaderTestFlow : FlowLogic() { 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) } -} \ No newline at end of file +} diff --git a/node/src/test/kotlin/net/corda/node/cordapp/CordappProviderImplTests.kt b/node/src/test/kotlin/net/corda/node/cordapp/CordappProviderImplTests.kt new file mode 100644 index 0000000000..762761d99e --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/cordapp/CordappProviderImplTests.kt @@ -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) + } +} diff --git a/node/src/test/kotlin/net/corda/node/cordapp/CordappProviderTests.kt b/node/src/test/kotlin/net/corda/node/cordapp/CordappProviderTests.kt deleted file mode 100644 index 4729118d6f..0000000000 --- a/node/src/test/kotlin/net/corda/node/cordapp/CordappProviderTests.kt +++ /dev/null @@ -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())) - } -} \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index 4d841f73b4..8b3eded971 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -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 { diff --git a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt index cc6be02581..944b525fac 100644 --- a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt @@ -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<*> { diff --git a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt index 785b7a8237..0350935ec8 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt @@ -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 { diff --git a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt index e39766768a..35bdcd0231 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt @@ -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().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> = 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 }) } diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt index 197022470d..a5ac113dc6 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt @@ -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() { diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt index f979021fce..0481ca51cd 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt @@ -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() diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt index 3ceb50a2e7..1e62a91eef 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt @@ -66,7 +66,7 @@ class FlowFrameworkTests { } } - private val mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin()) + private lateinit var mockNet: MockNetwork private val receivedSessionMessages = ArrayList() private lateinit var node1: StartedNode private lateinit var node2: StartedNode @@ -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 diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt index 9c910d973e..7e4781ba30 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt @@ -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 diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt index 407914ee08..52a2165960 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt @@ -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 diff --git a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt index 58d3651e4b..b5c9dc19d7 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt @@ -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 { diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt index a1d0e7bd0c..b6aabe44c6 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt @@ -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 diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt index a9afc22b35..9c969f0950 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt @@ -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 diff --git a/node/src/test/resources/net/corda/node/cordapp/isolated.jar b/node/src/test/resources/net/corda/node/cordapp/isolated.jar index 5a068dfd9b6348c1f0e926abb7808ff2fd050722..408e70145dbb633f6b5460bb374aa1ac399f3bcf 100644 GIT binary patch delta 8761 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 X=Uak#-tz$f;`3k5`B@_M#npcRv~H~} diff --git a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt index f33f151428..a6fbb33bd5 100644 --- a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt +++ b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt @@ -185,8 +185,8 @@ val ATTACHMENT_PROGRAM_ID = "net.corda.attachmentdemo.AttachmentContract" class AttachmentContract : Contract { override fun verify(tx: LedgerTransaction) { val state = tx.outputsOfType().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() diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/RatesFixFlow.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/RatesFixFlow.kt index b4646ff7be..285d477a69 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/RatesFixFlow.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/RatesFixFlow.kt @@ -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(SignRequest(partialMerkleTx)) return resp.unwrap { sig -> check(oracleSession.counterparty.owningKey.isFulfilledBy(listOf(sig.by))) - tx.toWireTransaction().checkSignature(sig) + tx.toWireTransaction(serviceHub).checkSignature(sig) sig } } diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt index 93f0bcaf20..89ea1c510b 100644 --- a/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt +++ b/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt @@ -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 -> true @@ -134,7 +136,7 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() { val ftx1 = wtx1.buildFilteredTransaction(Predicate(::filterAllOutputs)) assertFailsWith { 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 { 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 { 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 { 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 { 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() diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt index 12eeecd869..24bcd7970d 100644 --- a/samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt +++ b/samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt @@ -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() 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() 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() 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() diff --git a/samples/network-visualiser/src/test/kotlin/net/corda/netmap/simulation/IRSSimulationTest.kt b/samples/network-visualiser/src/test/kotlin/net/corda/netmap/simulation/IRSSimulationTest.kt index 8d9f7284d4..7a329a9ad4 100644 --- a/samples/network-visualiser/src/test/kotlin/net/corda/netmap/simulation/IRSSimulationTest.kt +++ b/samples/network-visualiser/src/test/kotlin/net/corda/netmap/simulation/IRSSimulationTest.kt @@ -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) diff --git a/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt b/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt index dec1031348..a81b898920 100644 --- a/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt +++ b/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt @@ -11,11 +11,15 @@ import net.corda.testing.DUMMY_NOTARY import net.corda.testing.IntegrationTestCategory import net.corda.testing.driver.driver import net.corda.testing.http.HttpApi +import net.corda.testing.setCordappPackages +import net.corda.testing.unsetCordappPackages import net.corda.vega.api.PortfolioApi import net.corda.vega.api.PortfolioApiUtils import net.corda.vega.api.SwapDataModel import net.corda.vega.api.SwapDataView import org.assertj.core.api.Assertions.assertThat +import org.junit.After +import org.junit.Before import org.junit.Test import java.math.BigDecimal import java.time.LocalDate @@ -29,6 +33,16 @@ class SimmValuationTest : IntegrationTestCategory { val testTradeId = "trade1" } + @Before + fun setup() { + setCordappPackages("net.corda.vega.contracts") + } + + @After + fun tearDown() { + unsetCordappPackages() + } + @Test fun `runs SIMM valuation demo`() { driver(isDebug = true) { diff --git a/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt b/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt index 9cc6f89bc7..2625e43280 100644 --- a/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt +++ b/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt @@ -15,14 +15,29 @@ import net.corda.nodeapi.User import net.corda.testing.* import net.corda.testing.driver.poll import net.corda.testing.node.NodeBasedTest +import net.corda.testing.setCordappPackages +import net.corda.testing.unsetCordappPackages import net.corda.traderdemo.flow.BuyerFlow import net.corda.traderdemo.flow.CommercialPaperIssueFlow import net.corda.traderdemo.flow.SellerFlow import org.assertj.core.api.Assertions.assertThat +import org.junit.After +import org.junit.Before import org.junit.Test import java.util.concurrent.Executors class TraderDemoTest : NodeBasedTest() { + + @Before + fun setup() { + setCordappPackages("net.corda.finance.contracts.asset", "net.corda.finance.contracts") + } + + @After + fun tearDown() { + unsetCordappPackages() + } + @Test fun `runs trader demo`() { val demoUser = User("demo", "demo", setOf(startFlowPermission())) diff --git a/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/TransactionGraphSearchTests.kt b/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/TransactionGraphSearchTests.kt index afcb0e37ac..0dc82f00f4 100644 --- a/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/TransactionGraphSearchTests.kt +++ b/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/TransactionGraphSearchTests.kt @@ -32,8 +32,8 @@ class TransactionGraphSearchTests : TestDependencyInjectionBase() { * @param signer signer for the two transactions and their commands. */ fun buildTransactions(command: CommandData): GraphTransactionStorage { - val megaCorpServices = MockServices(MEGA_CORP_KEY) - val notaryServices = MockServices(DUMMY_NOTARY_KEY) + val megaCorpServices = MockServices(listOf("net.corda.testing.contracts"), MEGA_CORP_KEY) + val notaryServices = MockServices(listOf("net.corda.testing.contracts"), DUMMY_NOTARY_KEY) val originBuilder = TransactionBuilder(DUMMY_NOTARY) .addOutputState(DummyState(random31BitValue()), DUMMY_PROGRAM_ID) diff --git a/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt b/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt index 93d60bcf25..b29cd52654 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt @@ -1,6 +1,7 @@ package net.corda.node.testing import com.codahale.metrics.MetricRegistry +import net.corda.core.cordapp.CordappProvider import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowLogic import net.corda.core.identity.Party @@ -8,6 +9,8 @@ import net.corda.core.node.NodeInfo import net.corda.core.node.services.* import net.corda.core.serialization.SerializeAsToken import net.corda.node.internal.InitiatedFlowFactory +import net.corda.node.internal.cordapp.CordappLoader +import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.node.serialization.NodeClock import net.corda.node.services.api.* import net.corda.node.services.config.NodeConfiguration @@ -24,7 +27,7 @@ import net.corda.testing.node.MockAttachmentStorage import net.corda.testing.node.MockNetworkMapCache import net.corda.testing.node.MockStateMachineRecordedTransactionMappingStorage import net.corda.testing.node.MockTransactionStorage -import java.security.PublicKey +import java.nio.file.Paths import java.sql.Connection import java.time.Clock @@ -44,7 +47,8 @@ open class MockServiceHubInternal( val overrideClock: Clock? = NodeClock(), val schemas: SchemaService? = NodeSchemaService(), val customContractUpgradeService: ContractUpgradeService? = null, - val customTransactionVerifierService: TransactionVerifierService? = InMemoryTransactionVerifierService(2) + val customTransactionVerifierService: TransactionVerifierService? = InMemoryTransactionVerifierService(2), + override val cordappProvider: CordappProvider = CordappProviderImpl(CordappLoader.createDefault(Paths.get("."))).start(attachments) ) : ServiceHubInternal { override val vaultQueryService: VaultQueryService get() = customVaultQuery ?: throw UnsupportedOperationException() diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt index 9b8718bc40..088eaaf6f0 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt @@ -9,6 +9,7 @@ import net.corda.core.node.ServiceHub import net.corda.core.transactions.TransactionBuilder import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.VerifierType +import net.corda.testing.node.MockCordappProvider import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 01ae3fbbf2..c65f5ffcea 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -501,6 +501,7 @@ class ShutdownManager(private val executorService: ExecutorService) { } fun shutdown() { + unsetCordappPackages() val shutdownActionFutures = state.locked { if (isShutdown) { emptyList Unit>>() @@ -591,7 +592,7 @@ class DriverDSL( val executorService get() = _executorService!! private var _shutdownManager: ShutdownManager? = null override val shutdownManager get() = _shutdownManager!! - private val packagesToScanString = (extraCordappPackagesToScan + getCallerPackage()).joinToString(",") + private val packagesToScanString = extraCordappPackagesToScan + getCallerPackage() class State { val processes = ArrayList>() @@ -791,7 +792,7 @@ class DriverDSL( _executorService = Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build()) _shutdownManager = ShutdownManager(executorService) // We set this property so that in-process nodes find cordapps. Out-of-process nodes need this passed in when started. - System.setProperty("net.corda.node.cordapp.scan.packages", packagesToScanString) + setCordappPackages(*packagesToScanString.toTypedArray()) if (networkMapStartStrategy.startDedicated) { startDedicatedNetworkMapService().andForget(log) // Allow it to start concurrently with other nodes. } @@ -845,7 +846,7 @@ class DriverDSL( } } else { val debugPort = if (isDebug) debugPortAllocation.nextPort() else null - val processFuture = startOutOfProcessNode(executorService, nodeConfiguration, config, quasarJarPath, debugPort, systemProperties, packagesToScanString) + val processFuture = startOutOfProcessNode(executorService, nodeConfiguration, config, quasarJarPath, debugPort, systemProperties, packagesToScanString.joinToString(",")) registerProcess(processFuture) return processFuture.flatMap { process -> val processDeathFuture = poll(executorService, "process death") { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index bb2ccf5c05..73dc1f883d 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -1,5 +1,6 @@ package net.corda.testing.node +import net.corda.core.cordapp.CordappProvider import net.corda.core.crypto.* import net.corda.core.flows.StateMachineRunId import net.corda.core.identity.PartyAndCertificate @@ -11,8 +12,8 @@ import net.corda.core.schemas.MappedSchema import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.transactions.SignedTransaction -import net.corda.core.utilities.NonEmptySet import net.corda.node.VersionInfo +import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage import net.corda.node.services.api.WritableTransactionStorage import net.corda.node.services.identity.InMemoryIdentityService @@ -38,15 +39,11 @@ import java.sql.Connection import java.time.Clock import java.util.* -// TODO: We need a single, rationalised unit testing environment that is usable for everything. Fix this! -// That means it probably shouldn't be in the 'core' module, which lacks enough code to create a realistic test env. - /** * A singleton utility that only provides a mock identity, key and storage service. However, this is sufficient for * building chains of transactions and verifying them. It isn't sufficient for testing flows however. */ -open class MockServices(vararg val keys: KeyPair) : ServiceHub { - +open class MockServices(cordappPackages: List = emptyList(), vararg val keys: KeyPair) : ServiceHub { companion object { @JvmStatic @@ -100,14 +97,15 @@ open class MockServices(vararg val keys: KeyPair) : ServiceHub { @JvmStatic fun makeTestDatabaseAndMockServices(customSchemas: Set = emptySet(), keys: List = listOf(MEGA_CORP_KEY), - createIdentityService: () -> IdentityService = { makeTestIdentityService() }): Pair { + createIdentityService: () -> IdentityService = { makeTestIdentityService() }, + cordappPackages: List = emptyList()): Pair { val dataSourceProps = makeTestDataSourceProperties() val databaseProperties = makeTestDatabaseProperties() val createSchemaService = { NodeSchemaService(customSchemas) } val identityServiceRef: IdentityService by lazy { createIdentityService() } val database = configureDatabase(dataSourceProps, databaseProperties, createSchemaService, { identityServiceRef }) val mockService = database.transaction { - object : MockServices(*(keys.toTypedArray())) { + object : MockServices(cordappPackages, *(keys.toTypedArray())) { override val identityService: IdentityService = database.transaction { identityServiceRef } override val vaultService: VaultService = makeVaultService(database.hibernateConfig) @@ -128,7 +126,9 @@ open class MockServices(vararg val keys: KeyPair) : ServiceHub { } } - constructor() : this(generateKeyPair()) + constructor(vararg keys: KeyPair) : this(emptyList(), *keys) + + constructor() : this(emptyList(), generateKeyPair()) val key: KeyPair get() = keys.first() @@ -141,7 +141,7 @@ open class MockServices(vararg val keys: KeyPair) : ServiceHub { } } - override val attachments: AttachmentStorage = MockAttachmentStorage() + final override val attachments: AttachmentStorage = MockAttachmentStorage() override val validatedTransactions: WritableTransactionStorage = MockTransactionStorage() val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage() override val identityService: IdentityService = InMemoryIdentityService(MOCK_IDENTITIES, trustRoot = DEV_TRUST_ROOT) @@ -157,6 +157,8 @@ open class MockServices(vararg val keys: KeyPair) : ServiceHub { return NodeInfo(emptyList(), listOf(identity), 1, serial = 1L) } override val transactionVerifierService: TransactionVerifierService get() = InMemoryTransactionVerifierService(2) + val mockCordappProvider: MockCordappProvider = MockCordappProvider(CordappLoader.createWithTestPackages(cordappPackages + CordappLoader.testPackages)).start(attachments) as MockCordappProvider + override val cordappProvider: CordappProvider = mockCordappProvider lateinit var hibernatePersister: HibernateObserver diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt index 78d66ea45e..5a55586924 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt @@ -17,6 +17,7 @@ import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.loggerFor import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER +import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.services.config.configureDevKeyAndTrustStores import net.corda.node.services.identity.InMemoryIdentityService import net.corda.node.utilities.CertificateAndKeyPair @@ -165,3 +166,18 @@ fun NodeInfo.chooseIdentityAndCert(): PartyAndCertificate = legalIdentitiesAndCe fun NodeInfo.chooseIdentity(): Party = chooseIdentityAndCert().party /** Returns the identity of the first notary found on the network */ fun ServiceHub.getDefaultNotary(): Party = networkMapCache.notaryIdentities.first() + +/** + * Set the package to scan for cordapps - this overrides the default behaviour of scanning the cordapps directory + * @param packageName A package name that you wish to scan for cordapps + */ +fun setCordappPackages(vararg packageNames: String) { + CordappLoader.testPackages = packageNames.toList() +} + +/** + * Unsets the default overriding behaviour of [setCordappPackage] + */ +fun unsetCordappPackages() { + CordappLoader.testPackages = emptyList() +} diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/TestDSL.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/TestDSL.kt index c4ed78bb77..72ac62eb04 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/TestDSL.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/TestDSL.kt @@ -1,6 +1,7 @@ package net.corda.testing import net.corda.core.contracts.* +import net.corda.core.cordapp.CordappProvider import net.corda.core.crypto.* import net.corda.core.crypto.NullKeys.NULL_SIGNATURE import net.corda.core.crypto.CompositeKey @@ -11,6 +12,7 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.WireTransaction import net.corda.testing.contracts.DUMMY_PROGRAM_ID import net.corda.testing.contracts.DummyContract +import net.corda.testing.node.MockCordappProvider import java.io.InputStream import java.security.KeyPair import java.security.PublicKey @@ -72,6 +74,7 @@ data class TestTransactionDSLInterpreter private constructor( val services = object : ServiceHub by ledgerInterpreter.services { override fun loadState(stateRef: StateRef) = ledgerInterpreter.resolveStateRef(stateRef) + override val cordappProvider: CordappProvider = ledgerInterpreter.services.cordappProvider } private fun copy(): TestTransactionDSLInterpreter = @@ -81,15 +84,21 @@ data class TestTransactionDSLInterpreter private constructor( labelToIndexMap = HashMap(labelToIndexMap) ) - internal fun toWireTransaction() = transactionBuilder.toWireTransaction() + internal fun toWireTransaction() = transactionBuilder.toWireTransaction(services) override fun input(stateRef: StateRef) { val state = ledgerInterpreter.resolveStateRef(stateRef) transactionBuilder.addInputState(StateAndRef(state, stateRef)) } - override fun _output(contractClassName: ContractClassName, label: String?, notary: Party, encumbrance: Int?, contractState: ContractState) { - transactionBuilder.addOutputState(contractState, contractClassName, notary, encumbrance) + override fun _output(contractClassName: ContractClassName, + label: String?, + notary: Party, + encumbrance: Int?, + attachmentConstraint: AttachmentConstraint, + contractState: ContractState + ) { + transactionBuilder.addOutputState(contractState, contractClassName, notary, encumbrance, attachmentConstraint) if (label != null) { if (label in labelToIndexMap) { throw DuplicateOutputLabel(label) @@ -125,6 +134,10 @@ data class TestTransactionDSLInterpreter private constructor( override fun tweak( dsl: TransactionDSL.() -> EnforceVerifyOrFail ) = dsl(TransactionDSL(copy())) + + override fun _attachment(contractClassName: ContractClassName) { + (services.cordappProvider as MockCordappProvider).addMockCordapp(contractClassName, services) + } } data class TestLedgerDSLInterpreter private constructor( diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/TransactionDSLInterpreter.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/TransactionDSLInterpreter.kt index 8b7d382c3c..22b70bc15a 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/TransactionDSLInterpreter.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/TransactionDSLInterpreter.kt @@ -1,7 +1,6 @@ package net.corda.testing import net.corda.core.contracts.* -import net.corda.testing.contracts.DummyContract import net.corda.core.crypto.SecureHash import net.corda.core.identity.Party import net.corda.core.utilities.seconds @@ -9,7 +8,6 @@ import net.corda.core.transactions.TransactionBuilder import java.security.PublicKey import java.time.Duration import java.time.Instant -import java.util.* /** * This interface defines the bare bone functionality that a Transaction DSL interpreter should implement. @@ -33,10 +31,16 @@ interface TransactionDSLInterpreter : Verifies, OutputStateLookup { * @param label An optional label that may be later used to retrieve the output probably in other transactions. * @param notary The associated notary. * @param encumbrance The position of the encumbrance state. + * @param attachmentConstraint The attachment constraint * @param contractState The state itself. * @params contractClassName The class name of the contract that verifies this state. */ - fun _output(contractClassName: ContractClassName, label: String?, notary: Party, encumbrance: Int?, contractState: ContractState) + fun _output(contractClassName: ContractClassName, + label: String?, + notary: Party, + encumbrance: Int?, + attachmentConstraint: AttachmentConstraint, + contractState: ContractState) /** * Adds an [Attachment] reference to the transaction. @@ -62,6 +66,12 @@ interface TransactionDSLInterpreter : Verifies, OutputStateLookup { * @param dsl The transaction DSL to be interpreted using the copy. */ fun tweak(dsl: TransactionDSL.() -> EnforceVerifyOrFail): EnforceVerifyOrFail + + /** + * Attaches an attachment containing the named contract to the transaction + * @param contractClassName The contract class to attach + */ + fun _attachment(contractClassName: ContractClassName) } class TransactionDSL(val interpreter: T) : TransactionDSLInterpreter by interpreter { @@ -78,7 +88,7 @@ class TransactionDSL(val interpreter: T) : Tr */ fun input(contractClassName: ContractClassName, state: ContractState) { val transaction = ledgerInterpreter._unverifiedTransaction(null, TransactionBuilder(notary = DUMMY_NOTARY)) { - output(contractClassName) { state } + output(contractClassName, attachmentConstraint = AlwaysAcceptAttachmentConstraint) { state } } input(transaction.outRef(0).ref) } @@ -89,17 +99,24 @@ class TransactionDSL(val interpreter: T) : Tr * @see TransactionDSLInterpreter._output */ @JvmOverloads - fun output(contractClassName: ContractClassName, label: String? = null, notary: Party = DUMMY_NOTARY, encumbrance: Int? = null, contractStateClosure: () -> ContractState) = - _output(contractClassName, label, notary, encumbrance, contractStateClosure()) + fun output(contractClassName: ContractClassName, + label: String? = null, + notary: Party = DUMMY_NOTARY, + encumbrance: Int? = null, + attachmentConstraint: AttachmentConstraint = AutomaticHashConstraint, + contractStateClosure: () -> ContractState) = + _output(contractClassName, label, notary, encumbrance, attachmentConstraint, contractStateClosure()) /** * @see TransactionDSLInterpreter._output */ - fun output(contractClassName: ContractClassName, label: String, contractState: ContractState) = - _output(contractClassName, label, DUMMY_NOTARY, null, contractState) + @JvmOverloads + fun output(contractClassName: ContractClassName, label: String, contractState: ContractState, attachmentConstraint: AttachmentConstraint = AutomaticHashConstraint) = + _output(contractClassName, label, DUMMY_NOTARY, null, attachmentConstraint, contractState) - fun output(contractClassName: ContractClassName, contractState: ContractState) = - _output(contractClassName,null, DUMMY_NOTARY, null, contractState) + @JvmOverloads + fun output(contractClassName: ContractClassName, contractState: ContractState, attachmentConstraint: AttachmentConstraint = AutomaticHashConstraint) = + _output(contractClassName,null, DUMMY_NOTARY, null, attachmentConstraint, contractState) /** * @see TransactionDSLInterpreter._command @@ -120,4 +137,11 @@ class TransactionDSL(val interpreter: T) : Tr @JvmOverloads fun timeWindow(time: Instant, tolerance: Duration = 30.seconds) = timeWindow(TimeWindow.withTolerance(time, tolerance)) + + /** + * @see TransactionDSLInterpreter._contractAttachment + */ + fun attachment(contractClassName: ContractClassName) = _attachment(contractClassName) + + fun attachments(vararg contractClassNames: ContractClassName) = contractClassNames.forEach { attachment(it)} } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContract.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContract.kt index 2f9da356a4..6afae9a82c 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContract.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContract.kt @@ -5,6 +5,7 @@ import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder +import kotlin.reflect.jvm.jvmName // The dummy contract doesn't do anything useful. It exists for testing purposes, but has to be serializable diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV2.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV2.kt index cf7c7ddd34..fca0db4cc1 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV2.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV2.kt @@ -3,6 +3,7 @@ package net.corda.testing.contracts import net.corda.core.contracts.* import net.corda.core.flows.ContractUpgradeFlow import net.corda.core.identity.AbstractParty +import net.corda.core.node.ServicesForResolution import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.WireTransaction @@ -40,9 +41,10 @@ class DummyContractV2 : UpgradedContract): Pair> { + fun generateUpgradeFromV1(services: ServicesForResolution, vararg states: StateAndRef): Pair> { val notary = states.map { it.state.notary }.single() require(states.isNotEmpty()) @@ -50,9 +52,9 @@ class DummyContractV2 : UpgradedContract>() + + fun addMockCordapp(contractClassName: ContractClassName, services: ServiceHub) { + val cordapp = CordappImpl(listOf(contractClassName), emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), Paths.get(".").toUri().toURL()) + if (cordappRegistry.none { it.first.contractClassNames.contains(contractClassName) }) { + cordappRegistry.add(Pair(cordapp, findOrImportAttachment(contractClassName.toByteArray(), services))) + } + } + + override fun getContractAttachmentID(contractClassName: ContractClassName): AttachmentId? = cordappRegistry.find { it.first.contractClassNames.contains(contractClassName) }?.second ?: super.getContractAttachmentID(contractClassName) + + private fun findOrImportAttachment(data: ByteArray, services: ServiceHub): AttachmentId { + return if (services.attachments is MockAttachmentStorage) { + val existingAttachment = (services.attachments as MockAttachmentStorage).files.filter { + Arrays.equals(it.value, data) + } + if (!existingAttachment.isEmpty()) { + existingAttachment.keys.first() + } else { + services.attachments.importAttachment(data.inputStream()) + } + } else { + throw Exception("MockCordappService only requires MockAttachmentStorage") + } + } +} diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt index 2eb87db58a..963e0ac461 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt @@ -4,14 +4,13 @@ import net.corda.client.mock.Generator import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.crypto.entropyToKeyPair -import net.corda.core.crypto.sha256 import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party -import net.corda.core.internal.AbstractAttachment import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.WireTransaction +import net.corda.nodeapi.internal.serialization.GeneratedAttachment import net.corda.testing.contracts.DUMMY_PROGRAM_ID import java.math.BigInteger import java.security.PublicKey @@ -32,21 +31,29 @@ data class GeneratedLedger( val hashTransactionMap: Map by lazy { transactions.associateBy(WireTransaction::id) } val attachmentMap: Map by lazy { attachments.associateBy(Attachment::id) } val identityMap: Map by lazy { identities.associateBy(Party::owningKey) } + val contractAttachmentMap: Map by lazy { + attachments.mapNotNull { it as? ContractAttachment }.associateBy { it.contract } + } companion object { val empty = GeneratedLedger(emptyList(), emptyMap(), emptySet(), emptySet()) + val contractAttachment = ContractAttachment(GeneratedAttachment(ByteArray(0) { 0 }), DUMMY_PROGRAM_ID) } fun resolveWireTransaction(transaction: WireTransaction): LedgerTransaction { return transaction.toLedgerTransaction( resolveIdentity = { identityMap[it] }, resolveAttachment = { attachmentMap[it] }, - resolveStateRef = { hashTransactionMap[it.txhash]?.outputs?.get(it.index) } + resolveStateRef = { hashTransactionMap[it.txhash]?.outputs?.get(it.index) }, + resolveContractAttachment = { contractAttachmentMap[it.contract]?.id } ) } val attachmentsGenerator: Generator> by lazy { - Generator.replicatePoisson(1.0, pickOneOrMaybeNew(attachments, attachmentGenerator)) + // TODO generate contract attachments properly + val dummyAttachment = Generator.pure(contractAttachment) + val otherAttachments = Generator.replicatePoisson(1.0, pickOneOrMaybeNew(attachments, attachmentGenerator)) + dummyAttachment.combine(otherAttachments) { dummy, other -> other + dummy } } val commandsGenerator: Generator, Party>>> by lazy { @@ -62,7 +69,7 @@ data class GeneratedLedger( Generator.sequence( outputs.map { output -> pickOneOrMaybeNew(identities, partyGenerator).map { notary -> - TransactionState(output, DUMMY_PROGRAM_ID, notary, null) + TransactionState(output, DUMMY_PROGRAM_ID, notary, null, HashAttachmentConstraint(contractAttachment.id)) } } ) @@ -127,7 +134,7 @@ data class GeneratedLedger( fun regularTransactionGenerator(inputNotary: Party, inputsToChooseFrom: List>): Generator> { val outputsGen = outputsGenerator.map { outputs -> outputs.map { output -> - TransactionState(output, DUMMY_PROGRAM_ID, inputNotary, null) + TransactionState(output, DUMMY_PROGRAM_ID, inputNotary, null, HashAttachmentConstraint(contractAttachment.id)) } } val inputsGen = Generator.sampleBernoulli(inputsToChooseFrom) @@ -182,10 +189,6 @@ data class GeneratedState( override val participants: List ) : ContractState -class GeneratedAttachment(bytes: ByteArray) : AbstractAttachment({ bytes }) { - override val id = bytes.sha256() -} - class GeneratedCommandData( val nonce: Long ) : CommandData diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt index 466b540f6a..f9965a5ffa 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt @@ -25,7 +25,7 @@ class VerifierTests { var currentLedger = GeneratedLedger.empty val transactions = ArrayList() val random = SplittableRandom() - for (i in 0..number - 1) { + for (i in 0 until number) { val (tx, ledger) = currentLedger.transactionGenerator.generateOrFail(random) transactions.add(tx) currentLedger = ledger @@ -35,7 +35,7 @@ class VerifierTests { @Test fun `single verifier works with requestor`() { - verifierDriver { + verifierDriver(extraCordappPackagesToScan = listOf("net.corda.finance.contracts")) { val aliceFuture = startVerificationRequestor(ALICE.name) val transactions = generateTransactions(100) val alice = aliceFuture.get() @@ -112,7 +112,10 @@ class VerifierTests { @Test fun `single verifier works with a node`() { - verifierDriver(networkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = true)) { + verifierDriver( + networkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = true), + extraCordappPackagesToScan = listOf("net.corda.finance.contracts") + ) { val aliceFuture = startNode(providedName = ALICE.name) val notaryFuture = startNode(providedName = DUMMY_NOTARY.name, advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type)), verifierType = VerifierType.OutOfProcess) val alice = aliceFuture.get()