From 8ae92850c9c6751ed1d24001982ee3b3133009d7 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Tue, 10 Oct 2017 13:10:21 +0100 Subject: [PATCH 01/15] State and Contract for Cash and CommercialPaper copied to perftestflows --- perftestflows/build.gradle | 43 ++ .../contracts/IPtCommercialPaperState.java | 23 + .../ptflows/contracts/PtCommercialPaper.kt | 197 +++++++++ .../corda/ptflows/contracts/asset/PtCash.kt | 415 ++++++++++++++++++ .../contracts/asset/PtOnLedgerAsset.kt | 323 ++++++++++++++ .../corda/ptflows/schemas/PtCashSchemaV1.kt | 45 ++ .../schemas/PtCommercialPaperSchemaV1.kt | 52 +++ .../ptflows/utils/StateSummingUtilities.kt | 39 ++ settings.gradle | 1 + 9 files changed, 1138 insertions(+) create mode 100644 perftestflows/build.gradle create mode 100644 perftestflows/src/main/java/net/corda/ptflows/contracts/IPtCommercialPaperState.java create mode 100644 perftestflows/src/main/kotlin/net/corda/ptflows/contracts/PtCommercialPaper.kt create mode 100644 perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/PtCash.kt create mode 100644 perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/PtOnLedgerAsset.kt create mode 100644 perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCashSchemaV1.kt create mode 100644 perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCommercialPaperSchemaV1.kt create mode 100644 perftestflows/src/main/kotlin/net/corda/ptflows/utils/StateSummingUtilities.kt diff --git a/perftestflows/build.gradle b/perftestflows/build.gradle new file mode 100644 index 0000000000..d05eafb3ac --- /dev/null +++ b/perftestflows/build.gradle @@ -0,0 +1,43 @@ +apply plugin: 'kotlin' +// Java Persistence API support: create no-arg constructor +// see: http://stackoverflow.com/questions/32038177/kotlin-with-jpa-default-constructor-hell +apply plugin: 'kotlin-jpa' +apply plugin: CanonicalizerPlugin +apply plugin: 'net.corda.plugins.publish-utils' +apply plugin: 'net.corda.plugins.quasar-utils' +apply plugin: 'net.corda.plugins.cordformation' +//apply plugin: 'com.jfrog.artifactory' + +description 'Corda performance test modules' + +dependencies { + // Note the :perftestflows module is a CorDapp in its own right + // and CorDapps using :perftestflows features should use 'cordapp' not 'compile' linkage. + cordaCompile project(':core') + cordaCompile project(':confidential-identities') + + testCompile project(':test-utils') + testCompile project(path: ':core', configuration: 'testArtifacts') + testCompile "junit:junit:$junit_version" +} + +configurations { + testArtifacts.extendsFrom testRuntime +} + +task testJar(type: Jar) { + classifier "tests" + from sourceSets.test.output +} + +artifacts { + testArtifacts testJar +} + +jar { + baseName 'corda-ptflows' +} + +publish { + name jar.baseName +} diff --git a/perftestflows/src/main/java/net/corda/ptflows/contracts/IPtCommercialPaperState.java b/perftestflows/src/main/java/net/corda/ptflows/contracts/IPtCommercialPaperState.java new file mode 100644 index 0000000000..edf6362ba9 --- /dev/null +++ b/perftestflows/src/main/java/net/corda/ptflows/contracts/IPtCommercialPaperState.java @@ -0,0 +1,23 @@ +package net.corda.ptflows.contracts; + + +import net.corda.core.contracts.Amount; +import net.corda.core.contracts.ContractState; +import net.corda.core.contracts.Issued; +import net.corda.core.identity.AbstractParty; + +import java.time.Instant; +import java.util.Currency; + +/* This is an interface solely created to demonstrate that the same kotlin tests can be run against + * either a Java implementation of the CommercialPaper or a kotlin implementation. + * Normally one would not duplicate an implementation in different languages for obvious reasons, but it demonstrates that + * ultimately either language can be used against a common test framework (and therefore can be used for real). + */ +public interface IPtCommercialPaperState extends ContractState { + IPtCommercialPaperState withOwner(AbstractParty newOwner); + + IPtCommercialPaperState withFaceValue(Amount> newFaceValue); + + IPtCommercialPaperState withMaturityDate(Instant newMaturityDate); +} diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/PtCommercialPaper.kt b/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/PtCommercialPaper.kt new file mode 100644 index 0000000000..7c4825ecc1 --- /dev/null +++ b/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/PtCommercialPaper.kt @@ -0,0 +1,197 @@ +package net.corda.ptflows.contracts + + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.contracts.* +import net.corda.core.crypto.NullKeys.NULL_PARTY +import net.corda.core.utilities.toBase58String +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.Party +import net.corda.core.identity.PartyAndCertificate +import net.corda.core.internal.Emoji +import net.corda.core.node.ServiceHub +import net.corda.core.schemas.MappedSchema +import net.corda.core.schemas.PersistentState +import net.corda.core.schemas.QueryableState +import net.corda.core.transactions.LedgerTransaction +import net.corda.core.transactions.TransactionBuilder +import net.corda.ptflows.contracts.asset.PtCash +import net.corda.ptflows.schemas.PtCommercialPaperSchemaV1 +import net.corda.ptflows.utils.sumCashBy +import java.time.Instant +import java.util.* + +/** + * This is an ultra-trivial implementation of commercial paper, which is essentially a simpler version of a corporate + * bond. It can be seen as a company-specific currency. A company issues CP with a particular face value, say $100, + * but sells it for less, say $90. The paper can be redeemed for cash at a given date in the future. Thus this example + * would have a 10% interest rate with a single repayment. Commercial paper is often rolled over (the maturity date + * is adjusted as if the paper was redeemed and immediately repurchased, but without having to front the cash). + * + * This contract is not intended to realistically model CP. It is here only to act as a next step up above cash in + * the prototyping phase. It is thus very incomplete. + * + * Open issues: + * - In this model, you cannot merge or split CP. Can you do this normally? We could model CP as a specialised form + * of cash, or reuse some of the cash code? Waiting on response from Ayoub and Rajar about whether CP can always + * be split/merged or only in secondary markets. Even if current systems can't do this, would it be a desirable + * feature to have anyway? + * - The funding steps of CP is totally ignored in this model. + * - No attention is paid to the existing roles of custodians, funding banks, etc. + * - There are regional variations on the CP concept, for instance, American CP requires a special "CUSIP number" + * 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.ptflows.contracts.PtCommercialPaper" + +// TODO: Generalise the notion of an owned instrument into a superclass/supercontract. Consider composition vs inheritance. +class PtCommercialPaper : Contract { + companion object { + const val CP_PROGRAM_ID: ContractClassName = "net.corda.ptflows.contracts.PtCommercialPaper" + } + data class State( + val issuance: PartyAndReference, + override val owner: AbstractParty, + val faceValue: Amount>, + val maturityDate: Instant + ) : OwnableState, QueryableState, IPtCommercialPaperState{ + override val participants = listOf(owner) + + override fun withNewOwner(newOwner: AbstractParty) = CommandAndState(Commands.Move(), copy(owner = newOwner)) + fun withoutOwner() = copy(owner = NULL_PARTY) + override fun toString() = "${Emoji.newspaper}CommercialPaper(of $faceValue redeemable on $maturityDate by '$issuance', owned by $owner)" + + // Although kotlin is smart enough not to need these, as we are using the ICommercialPaperState, we need to declare them explicitly for use later, + override fun withOwner(newOwner: AbstractParty): IPtCommercialPaperState = copy(owner = newOwner) + + override fun withFaceValue(newFaceValue: Amount>): IPtCommercialPaperState = copy(faceValue = newFaceValue) + override fun withMaturityDate(newMaturityDate: Instant): IPtCommercialPaperState = copy(maturityDate = newMaturityDate) + + /** Object Relational Mapping support. */ + override fun supportedSchemas(): Iterable = listOf(PtCommercialPaperSchemaV1) + /** Additional used schemas would be added here (eg. CommercialPaperV2, ...) */ + + /** Object Relational Mapping support. */ + override fun generateMappedObject(schema: MappedSchema): PersistentState { + return when (schema) { + is PtCommercialPaperSchemaV1 -> PtCommercialPaperSchemaV1.PersistentCommercialPaperState( + issuanceParty = this.issuance.party.owningKey.toBase58String(), + issuanceRef = this.issuance.reference.bytes, + owner = this.owner.owningKey.toBase58String(), + maturity = this.maturityDate, + faceValue = this.faceValue.quantity, + currency = this.faceValue.token.product.currencyCode, + faceValueIssuerParty = this.faceValue.token.issuer.party.owningKey.toBase58String(), + faceValueIssuerRef = this.faceValue.token.issuer.reference.bytes + ) + /** Additional schema mappings would be added here (eg. CommercialPaperV2, ...) */ + else -> throw IllegalArgumentException("Unrecognised schema $schema") + } + } + + /** @suppress */ infix fun `owned by`(owner: AbstractParty) = copy(owner = owner) + } + + interface Commands : CommandData { + class Move : TypeOnlyCommandData(), Commands + + class Redeem : TypeOnlyCommandData(), Commands + // We don't need a nonce in the issue command, because the issuance.reference field should already be unique per CP. + // However, nothing in the platform enforces that uniqueness: it's up to the issuer. + class Issue : TypeOnlyCommandData(), Commands + } + + override fun verify(tx: LedgerTransaction) { + // Group by everything except owner: any modification to the CP at all is considered changing it fundamentally. + val groups = tx.groupStates(State::withoutOwner) + + // There are two possible things that can be done with this CP. The first is trading it. The second is redeeming + // it for cash on or after the maturity date. + val command = tx.commands.requireSingleCommand() + val timeWindow: TimeWindow? = tx.timeWindow + + // Suppress compiler warning as 'key' is an unused variable when destructuring 'groups'. + @Suppress("UNUSED_VARIABLE") + for ((inputs, outputs, key) in groups) { + when (command.value) { + is Commands.Move -> { + val input = inputs.single() + requireThat { + "the transaction is signed by the owner of the CP" using (input.owner.owningKey in command.signers) + "the state is propagated" using (outputs.size == 1) + // Don't need to check anything else, as if outputs.size == 1 then the output is equal to + // the input ignoring the owner field due to the grouping. + } + } + + is Commands.Redeem -> { + // Redemption of the paper requires movement of on-ledger cash. + val input = inputs.single() + val received = tx.outputStates.sumCashBy(input.owner) + val time = timeWindow?.fromTime ?: throw IllegalArgumentException("Redemptions must have a time-window") + requireThat { + "the paper must have matured" using (time >= input.maturityDate) + "the received amount equals the face value" using (received == input.faceValue) + "the paper must be destroyed" using outputs.isEmpty() + "the transaction is signed by the owner of the CP" using (input.owner.owningKey in command.signers) + } + } + + is Commands.Issue -> { + val output = outputs.single() + val time = timeWindow?.untilTime ?: throw IllegalArgumentException("Issuances have a time-window") + requireThat { + // Don't allow people to issue commercial paper under other entities identities. + "output states are issued by a command signer" using + (output.issuance.party.owningKey in command.signers) + "output values sum to more than the inputs" using (output.faceValue.quantity > 0) + "the maturity date is not in the past" using (time < output.maturityDate) + // Don't allow an existing CP state to be replaced by this issuance. + // TODO: Consider how to handle the case of mistaken issuances, or other need to patch. + "output values sum to more than the inputs" using inputs.isEmpty() + } + } + + // TODO: Think about how to evolve contracts over time with new commands. + else -> throw IllegalArgumentException("Unrecognised command") + } + } + } + + /** + * Returns a transaction that issues commercial paper, owned by the issuing parties key. Does not update + * an existing transaction because you aren't able to issue multiple pieces of CP in a single transaction + * at the moment: this restriction is not fundamental and may be lifted later. + */ + fun generateIssue(issuance: PartyAndReference, faceValue: Amount>, maturityDate: Instant, + notary: Party): TransactionBuilder { + val state = State(issuance, issuance.party, faceValue, maturityDate) + return TransactionBuilder(notary = notary).withItems(StateAndContract(state, CP_PROGRAM_ID), Command(Commands.Issue(), issuance.party.owningKey)) + } + + /** + * Updates the given partial transaction with an input/output/command to reassign ownership of the paper. + */ + fun generateMove(tx: TransactionBuilder, paper: StateAndRef, newOwner: AbstractParty) { + tx.addInputState(paper) + tx.addOutputState(paper.state.data.withOwner(newOwner), CP_PROGRAM_ID) + tx.addCommand(Commands.Move(), paper.state.data.owner.owningKey) + } + + /** + * Intended to be called by the issuer of some commercial paper, when an owner has notified us that they wish + * to redeem the paper. We must therefore send enough money to the key that owns the paper to satisfy the face + * value, and then ensure the paper is removed from the ledger. + * + * @throws InsufficientBalanceException if the vault doesn't contain enough money to pay the redeemer. + */ + @Throws(InsufficientBalanceException::class) + @Suspendable + fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef, services: ServiceHub, ourIdentity: PartyAndCertificate) { + // Add the cash movement using the states in our vault. + PtCash.generateSpend(services, tx, paper.state.data.faceValue.withoutIssuer(), ourIdentity, paper.state.data.owner) + tx.addInputState(paper) + tx.addCommand(Commands.Redeem(), paper.state.data.owner.owningKey) + } +} \ No newline at end of file diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/PtCash.kt b/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/PtCash.kt new file mode 100644 index 0000000000..768a17208a --- /dev/null +++ b/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/PtCash.kt @@ -0,0 +1,415 @@ +// So the static extension functions get put into a class with a better name than CashKt +package net.corda.ptflows.contracts.asset + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.contracts.* +import net.corda.core.contracts.Amount.Companion.sumOrThrow +import net.corda.core.crypto.NullKeys.NULL_PARTY +import net.corda.core.crypto.entropyToKeyPair +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party +import net.corda.core.identity.PartyAndCertificate +import net.corda.core.internal.Emoji +import net.corda.core.node.ServiceHub +import net.corda.core.schemas.MappedSchema +import net.corda.core.schemas.PersistentState +import net.corda.core.schemas.QueryableState +import net.corda.core.transactions.LedgerTransaction +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.toBase58String +import net.corda.ptflows.schemas.PtCashSchemaV1 +import net.corda.ptflows.utils.sumCash +import net.corda.ptflows.utils.sumCashOrNull +import net.corda.ptflows.utils.sumCashOrZero +import java.math.BigInteger +import java.security.PublicKey +import java.sql.DatabaseMetaData +import java.util.* +import java.util.concurrent.atomic.AtomicReference + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Cash +// + +/** + * Pluggable interface to allow for different cash selection provider implementations + * Default implementation [CashSelectionH2Impl] uses H2 database and a custom function within H2 to perform aggregation. + * Custom implementations must implement this interface and declare their implementation in + * META-INF/services/net.corda.contracts.asset.CashSelection + */ +interface PtCashSelection { + companion object { + val instance = AtomicReference() + + fun getInstance(metadata: () -> java.sql.DatabaseMetaData): PtCashSelection { + return instance.get() ?: { + val _metadata = metadata() + val cashSelectionAlgos = ServiceLoader.load(PtCashSelection::class.java).toList() + val cashSelectionAlgo = cashSelectionAlgos.firstOrNull { it.isCompatible(_metadata) } + cashSelectionAlgo?.let { + instance.set(cashSelectionAlgo) + cashSelectionAlgo + } ?: throw ClassNotFoundException("\nUnable to load compatible cash selection algorithm implementation for JDBC driver ($_metadata)." + + "\nPlease specify an implementation in META-INF/services/net.corda.finance.contracts.asset.CashSelection") + }.invoke() + } + } + + /** + * Upon dynamically loading configured Cash Selection algorithms declared in META-INF/services + * this method determines whether the loaded implementation is compatible and usable with the currently + * loaded JDBC driver. + * Note: the first loaded implementation to pass this check will be used at run-time. + */ + fun isCompatible(metadata: DatabaseMetaData): Boolean + + /** + * Query to gather Cash states that are available + * @param services The service hub to allow access to the database session + * @param amount The amount of currency desired (ignoring issues, but specifying the currency) + * @param onlyFromIssuerParties If empty the operation ignores the specifics of the issuer, + * otherwise the set of eligible states wil be filtered to only include those from these issuers. + * @param notary If null the notary source is ignored, if specified then only states marked + * with this notary are included. + * @param lockId The FlowLogic.runId.uuid of the flow, which is used to soft reserve the states. + * Also, previous outputs of the flow will be eligible as they are implicitly locked with this id until the flow completes. + * @param withIssuerRefs If not empty the specific set of issuer references to match against. + * @return The matching states that were found. If sufficient funds were found these will be locked, + * otherwise what is available is returned unlocked for informational purposes. + */ + @Suspendable + fun unconsumedCashStatesForSpending(services: ServiceHub, + amount: Amount, + onlyFromIssuerParties: Set = emptySet(), + notary: Party? = null, + lockId: UUID, + withIssuerRefs: Set = emptySet()): List> +} + +/** + * A cash transaction may split and merge money represented by a set of (issuer, depositRef) pairs, across multiple + * input and output states. Imagine a Bitcoin transaction but in which all UTXOs had a colour + * (a blend of issuer+depositRef) and you couldn't merge outputs of two colours together, but you COULD put them in + * the same transaction. + * + * The goal of this design is to ensure that money can be withdrawn from the ledger easily: if you receive some money + * via this contract, you always know where to go in order to extract it from the R3 ledger, no matter how many hands + * it has passed through in the intervening time. + * + * At the same time, other contracts that just want money and don't care much who is currently holding it in their + * vaults can ignore the issuer/depositRefs and just examine the amount fields. + */ +class PtCash : PtOnLedgerAsset() { + override fun extractCommands(commands: Collection>): List> + = commands.select() + + // DOCSTART 1 + /** A state representing a cash claim against some party. */ + data class State( + override val amount: Amount>, + + /** There must be a MoveCommand signed by this key to claim the amount. */ + override val owner: AbstractParty + ) : FungibleAsset, QueryableState { + constructor(deposit: PartyAndReference, amount: Amount, owner: AbstractParty) + : this(Amount(amount.quantity, Issued(deposit, amount.token)), owner) + + override val exitKeys = setOf(owner.owningKey, amount.token.issuer.party.owningKey) + override val participants = listOf(owner) + + override fun withNewOwnerAndAmount(newAmount: Amount>, newOwner: AbstractParty): FungibleAsset + = copy(amount = amount.copy(newAmount.quantity), owner = newOwner) + + override fun toString() = "${Emoji.bagOfCash}Cash($amount at ${amount.token.issuer} owned by $owner)" + + override fun withNewOwner(newOwner: AbstractParty) = CommandAndState(Commands.Move(), copy(owner = newOwner)) + fun ownedBy(owner: AbstractParty) = copy(owner = owner) + fun issuedBy(party: AbstractParty) = copy(amount = Amount(amount.quantity, amount.token.copy(issuer = amount.token.issuer.copy(party = party)))) + fun issuedBy(deposit: PartyAndReference) = copy(amount = Amount(amount.quantity, amount.token.copy(issuer = deposit))) + fun withDeposit(deposit: PartyAndReference): State = copy(amount = amount.copy(token = amount.token.copy(issuer = deposit))) + + /** Object Relational Mapping support. */ + override fun generateMappedObject(schema: MappedSchema): PersistentState { + return when (schema) { + is PtCashSchemaV1 -> PtCashSchemaV1.PersistentCashState( + owner = this.owner, + pennies = this.amount.quantity, + currency = this.amount.token.product.currencyCode, + issuerParty = this.amount.token.issuer.party.owningKey.toBase58String(), + issuerRef = this.amount.token.issuer.reference.bytes + ) + /** Additional schema mappings would be added here (eg. CashSchemaV2, CashSchemaV3, ...) */ + else -> throw IllegalArgumentException("Unrecognised schema $schema") + } + } + + /** Object Relational Mapping support. */ + override fun supportedSchemas(): Iterable = listOf(PtCashSchemaV1) + /** Additional used schemas would be added here (eg. CashSchemaV2, CashSchemaV3, ...) */ + } + // DOCEND 1 + + // Just for grouping + interface Commands : CommandData { + /** + * A command stating that money has been moved, optionally to fulfil another contract. + * + * @param contract the contract this move is for the attention of. Only that contract's verify function + * should take the moved states into account when considering whether it is valid. Typically this will be + * null. + */ + data class Move(override val contract: Class? = null) : MoveCommand + + /** + * Allows new cash states to be issued into existence. + */ + class Issue : TypeOnlyCommandData() + + /** + * A command stating that money has been withdrawn from the shared ledger and is now accounted for + * in some other way. + */ + data class Exit(val amount: Amount>) : CommandData + } + + /** + * Puts together an issuance transaction from the given template, that starts out being owned by the given pubkey. + */ + fun generateIssue(tx: TransactionBuilder, tokenDef: Issued, pennies: Long, owner: AbstractParty, notary: Party) + = generateIssue(tx, Amount(pennies, tokenDef), owner, notary) + + /** + * Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey. + */ + fun generateIssue(tx: TransactionBuilder, amount: Amount>, owner: AbstractParty, notary: Party) + = generateIssue(tx, TransactionState(State(amount, owner), PROGRAM_ID, notary), Commands.Issue()) + + override fun deriveState(txState: TransactionState, amount: Amount>, owner: AbstractParty) + = txState.copy(data = txState.data.copy(amount = amount, owner = owner)) + + override fun generateExitCommand(amount: Amount>) = Commands.Exit(amount) + override fun generateMoveCommand() = Commands.Move() + + override fun verify(tx: LedgerTransaction) { + // Each group is a set of input/output states with distinct (reference, currency) attributes. These types + // of cash are not fungible and must be kept separated for bookkeeping purposes. + val groups = tx.groupStates { it: State -> it.amount.token } + + for ((inputs, outputs, key) in groups) { + // Either inputs or outputs could be empty. + val issuer = key.issuer + val currency = key.product + + requireThat { + "there are no zero sized outputs" using (outputs.none { it.amount.quantity == 0L }) + } + + val issueCommand = tx.commands.select().firstOrNull() + if (issueCommand != null) { + verifyIssueCommand(inputs, outputs, tx, issueCommand, currency, issuer) + } else { + val inputAmount = inputs.sumCashOrNull() ?: throw IllegalArgumentException("there is at least one cash input for this group") + val outputAmount = outputs.sumCashOrZero(Issued(issuer, currency)) + + // If we want to remove cash from the ledger, that must be signed for by the issuer. + // A mis-signed or duplicated exit command will just be ignored here and result in the exit amount being zero. + val exitKeys: Set = inputs.flatMap { it.exitKeys }.toSet() + val exitCommand = tx.commands.select(parties = null, signers = exitKeys).filter { it.value.amount.token == key }.singleOrNull() + val amountExitingLedger = exitCommand?.value?.amount ?: Amount(0, Issued(issuer, currency)) + + requireThat { + "there are no zero sized inputs" using inputs.none { it.amount.quantity == 0L } + "for reference ${issuer.reference} at issuer ${issuer.party} the amounts balance: ${inputAmount.quantity} - ${amountExitingLedger.quantity} != ${outputAmount.quantity}" using + (inputAmount == outputAmount + amountExitingLedger) + } + + verifyMoveCommand(inputs, tx.commands) + } + } + } + + private fun verifyIssueCommand(inputs: List, + outputs: List, + tx: LedgerTransaction, + issueCommand: CommandWithParties, + currency: Currency, + issuer: PartyAndReference) { + // If we have an issue command, perform special processing: the group is allowed to have no inputs, + // and the output states must have a deposit reference owned by the signer. + // + // Whilst the transaction *may* have no inputs, it can have them, and in this case the outputs must + // sum to more than the inputs. An issuance of zero size is not allowed. + // + // Note that this means literally anyone with access to the network can issue cash claims of arbitrary + // amounts! It is up to the recipient to decide if the backing party is trustworthy or not, via some + // as-yet-unwritten identity service. See ADP-22 for discussion. + + // The grouping ensures that all outputs have the same deposit reference and currency. + val inputAmount = inputs.sumCashOrZero(Issued(issuer, currency)) + val outputAmount = outputs.sumCash() + val cashCommands = tx.commands.select() + requireThat { + // TODO: This doesn't work with the trader demo, so use the underlying key instead + // "output states are issued by a command signer" by (issuer.party in issueCommand.signingParties) + "output states are issued by a command signer" using (issuer.party.owningKey in issueCommand.signers) + "output values sum to more than the inputs" using (outputAmount > inputAmount) + "there is only a single issue command" using (cashCommands.count() == 1) + } + } + + companion object { + const val PROGRAM_ID: ContractClassName = "net.corda.finance.contracts.asset.Cash" + + /** + * Generate a transaction that moves an amount of currency to the given party, and sends any change back to + * sole identity of the calling node. Fails for nodes with multiple identities. + * + * Note: an [Amount] of [Currency] is only fungible for a given Issuer Party within a [FungibleAsset] + * + * @param services The [ServiceHub] to provide access to the database session. + * @param tx A builder, which may contain inputs, outputs and commands already. The relevant components needed + * to move the cash will be added on top. + * @param amount How much currency to send. + * @param to the recipient party. + * @param onlyFromParties if non-null, the asset states will be filtered to only include those issued by the set + * of given parties. This can be useful if the party you're trying to pay has expectations + * about which type of asset claims they are willing to accept. + * @return A [Pair] of the same transaction builder passed in as [tx], and the list of keys that need to sign + * the resulting transaction for it to be valid. + * @throws InsufficientBalanceException when a cash spending transaction fails because + * there is insufficient quantity for a given currency (and optionally set of Issuer Parties). + */ + @JvmStatic + @Throws(InsufficientBalanceException::class) + @Suspendable + @Deprecated("Our identity should be specified", replaceWith = ReplaceWith("generateSpend(services, tx, amount, to, ourIdentity, onlyFromParties)")) + fun generateSpend(services: ServiceHub, + tx: TransactionBuilder, + amount: Amount, + to: AbstractParty, + onlyFromParties: Set = emptySet()) = generateSpend(services, tx, listOf(PartyAndAmount(to, amount)), services.myInfo.legalIdentitiesAndCerts.single(), onlyFromParties) + + /** + * Generate a transaction that moves an amount of currency to the given party. + * + * Note: an [Amount] of [Currency] is only fungible for a given Issuer Party within a [FungibleAsset] + * + * @param services The [ServiceHub] to provide access to the database session. + * @param tx A builder, which may contain inputs, outputs and commands already. The relevant components needed + * to move the cash will be added on top. + * @param amount How much currency to send. + * @param to the recipient party. + * @param ourIdentity well known identity to create a new confidential identity from, for sending change to. + * @param onlyFromParties if non-null, the asset states will be filtered to only include those issued by the set + * of given parties. This can be useful if the party you're trying to pay has expectations + * about which type of asset claims they are willing to accept. + * @return A [Pair] of the same transaction builder passed in as [tx], and the list of keys that need to sign + * the resulting transaction for it to be valid. + * @throws InsufficientBalanceException when a cash spending transaction fails because + * there is insufficient quantity for a given currency (and optionally set of Issuer Parties). + */ + @JvmStatic + @Throws(InsufficientBalanceException::class) + @Suspendable + fun generateSpend(services: ServiceHub, + tx: TransactionBuilder, + amount: Amount, + ourIdentity: PartyAndCertificate, + to: AbstractParty, + onlyFromParties: Set = emptySet()): Pair> { + return generateSpend(services, tx, listOf(PartyAndAmount(to, amount)), ourIdentity, onlyFromParties) + } + + /** + * Generate a transaction that moves money of the given amounts to the recipients specified, and sends any change + * back to sole identity of the calling node. Fails for nodes with multiple identities. + * + * Note: an [Amount] of [Currency] is only fungible for a given Issuer Party within a [FungibleAsset] + * + * @param services The [ServiceHub] to provide access to the database session. + * @param tx A builder, which may contain inputs, outputs and commands already. The relevant components needed + * to move the cash will be added on top. + * @param payments A list of amounts to pay, and the party to send the payment to. + * @param onlyFromParties if non-null, the asset states will be filtered to only include those issued by the set + * of given parties. This can be useful if the party you're trying to pay has expectations + * about which type of asset claims they are willing to accept. + * @return A [Pair] of the same transaction builder passed in as [tx], and the list of keys that need to sign + * the resulting transaction for it to be valid. + * @throws InsufficientBalanceException when a cash spending transaction fails because + * there is insufficient quantity for a given currency (and optionally set of Issuer Parties). + */ + @JvmStatic + @Throws(InsufficientBalanceException::class) + @Suspendable + @Deprecated("Our identity should be specified", replaceWith = ReplaceWith("generateSpend(services, tx, amount, to, ourIdentity, onlyFromParties)")) + fun generateSpend(services: ServiceHub, + tx: TransactionBuilder, + payments: List>, + onlyFromParties: Set = emptySet()) = generateSpend(services, tx, payments, services.myInfo.legalIdentitiesAndCerts.single(), onlyFromParties) + + /** + * Generate a transaction that moves money of the given amounts to the recipients specified. + * + * Note: an [Amount] of [Currency] is only fungible for a given Issuer Party within a [FungibleAsset] + * + * @param services The [ServiceHub] to provide access to the database session. + * @param tx A builder, which may contain inputs, outputs and commands already. The relevant components needed + * to move the cash will be added on top. + * @param payments A list of amounts to pay, and the party to send the payment to. + * @param ourIdentity well known identity to create a new confidential identity from, for sending change to. + * @param onlyFromParties if non-null, the asset states will be filtered to only include those issued by the set + * of given parties. This can be useful if the party you're trying to pay has expectations + * about which type of asset claims they are willing to accept. + * @return A [Pair] of the same transaction builder passed in as [tx], and the list of keys that need to sign + * the resulting transaction for it to be valid. + * @throws InsufficientBalanceException when a cash spending transaction fails because + * there is insufficient quantity for a given currency (and optionally set of Issuer Parties). + */ + @JvmStatic + @Throws(InsufficientBalanceException::class) + @Suspendable + fun generateSpend(services: ServiceHub, + tx: TransactionBuilder, + payments: List>, + ourIdentity: PartyAndCertificate, + onlyFromParties: Set = emptySet()): Pair> { + fun deriveState(txState: TransactionState, amt: Amount>, owner: AbstractParty) + = txState.copy(data = txState.data.copy(amount = amt, owner = owner)) + + // Retrieve unspent and unlocked cash states that meet our spending criteria. + val totalAmount = payments.map { it.amount }.sumOrThrow() + val cashSelection = PtCashSelection.getInstance({ services.jdbcSession().metaData }) + val acceptableCoins = cashSelection.unconsumedCashStatesForSpending(services, totalAmount, onlyFromParties, tx.notary, tx.lockId) + val revocationEnabled = false // Revocation is currently unsupported + // Generate a new identity that change will be sent to for confidentiality purposes. This means that a + // third party with a copy of the transaction (such as the notary) cannot identify who the change was + // sent to + val changeIdentity = services.keyManagementService.freshKeyAndCert(ourIdentity, revocationEnabled) + return PtOnLedgerAsset.generateSpend(tx, payments, acceptableCoins, + changeIdentity.party.anonymise(), + { state, quantity, owner -> deriveState(state, quantity, owner) }, + { PtCash().generateMoveCommand() }) + } + } +} + +// Small DSL extensions. + +/** @suppress */ infix fun PtCash.State.`owned by`(owner: AbstractParty) = ownedBy(owner) +/** @suppress */ infix fun PtCash.State.`issued by`(party: AbstractParty) = issuedBy(party) +/** @suppress */ infix fun PtCash.State.`issued by`(deposit: PartyAndReference) = issuedBy(deposit) +/** @suppress */ infix fun PtCash.State.`with deposit`(deposit: PartyAndReference): PtCash.State = withDeposit(deposit) + +// Unit testing helpers. These could go in a separate file but it's hardly worth it for just a few functions. + +/** A randomly generated key. */ +val DUMMY_CASH_ISSUER_KEY by lazy { entropyToKeyPair(BigInteger.valueOf(10)) } +/** A dummy, randomly generated issuer party by the name of "Snake Oil Issuer" */ +val DUMMY_CASH_ISSUER by lazy { Party(CordaX500Name(organisation = "Snake Oil Issuer", locality = "London", country = "GB"), DUMMY_CASH_ISSUER_KEY.public).ref(1) } +/** An extension property that lets you write 100.DOLLARS.CASH */ +val Amount.CASH: PtCash.State get() = PtCash.State(Amount(quantity, Issued(DUMMY_CASH_ISSUER, token)), NULL_PARTY) +/** An extension property that lets you get a cash state from an issued token, under the [NULL_PARTY] */ +val Amount>.STATE: PtCash.State get() = PtCash.State(this, NULL_PARTY) diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/PtOnLedgerAsset.kt b/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/PtOnLedgerAsset.kt new file mode 100644 index 0000000000..ac0365ff3a --- /dev/null +++ b/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/PtOnLedgerAsset.kt @@ -0,0 +1,323 @@ +package net.corda.ptflows.contracts.asset + + +import net.corda.core.contracts.* +import net.corda.core.contracts.Amount.Companion.sumOrThrow +import net.corda.core.identity.AbstractParty +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.trace +import java.security.PublicKey +import java.util.* + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Generic contract for assets on a ledger +// + +/** A simple holder for a (possibly anonymous) [AbstractParty] and a quantity of tokens */ +data class PartyAndAmount(val party: AbstractParty, val amount: Amount) + +/** + * An asset transaction may split and merge assets represented by a set of (issuer, depositRef) pairs, across multiple + * input and output states. Imagine a Bitcoin transaction but in which all UTXOs had a colour (a blend of + * issuer+depositRef) and you couldn't merge outputs of two colours together, but you COULD put them in the same + * transaction. + * + * The goal of this design is to ensure that assets can be withdrawn from the ledger easily: if you receive some asset + * via this contract, you always know where to go in order to extract it from the R3 ledger, no matter how many hands + * it has passed through in the intervening time. + * + * At the same time, other contracts that just want assets and don't care much who is currently holding it can ignore + * the issuer/depositRefs and just examine the amount fields. + */ +abstract class PtOnLedgerAsset> : Contract { + companion object { + val log = loggerFor>() + + /** + * Generate a transaction that moves an amount of currency to the given pubkey. + * + * Note: an [Amount] of [Currency] is only fungible for a given Issuer Party within a [FungibleAsset] + * + * @param tx A builder, which may contain inputs, outputs and commands already. The relevant components needed + * to move the cash will be added on top. + * @param amount How much currency to send. + * @param to a key of the recipient. + * @param acceptableStates a list of acceptable input states to use. + * @param payChangeTo party to pay any change to; this is normally a confidential identity of the calling + * party. + * @param deriveState a function to derive an output state based on an input state, amount for the output + * and public key to pay to. + * @return A [Pair] of the same transaction builder passed in as [tx], and the list of keys that need to sign + * the resulting transaction for it to be valid. + * @throws InsufficientBalanceException when a cash spending transaction fails because + * there is insufficient quantity for a given currency (and optionally set of Issuer Parties). + */ + @Throws(InsufficientBalanceException::class) + @JvmStatic + fun , T: Any> generateSpend(tx: TransactionBuilder, + amount: Amount, + to: AbstractParty, + acceptableStates: List>, + payChangeTo: AbstractParty, + deriveState: (TransactionState, Amount>, AbstractParty) -> TransactionState, + generateMoveCommand: () -> CommandData): Pair> { + return generateSpend(tx, listOf(PartyAndAmount(to, amount)), acceptableStates, payChangeTo, deriveState, generateMoveCommand) + } + + /** + * Adds to the given transaction states that move amounts of a fungible asset to the given parties, using only + * the provided acceptable input states to find a solution (not all of them may be used in the end). A change + * output will be generated if the state amounts don't exactly fit. + * + * The fungible assets must all be of the same type and the amounts must be summable i.e. amounts of the same + * token. + * + * @param tx A builder, which may contain inputs, outputs and commands already. The relevant components needed + * to move the cash will be added on top. + * @param amount How much currency to send. + * @param to a key of the recipient. + * @param acceptableStates a list of acceptable input states to use. + * @param payChangeTo party to pay any change to; this is normally a confidential identity of the calling + * party. We use a new confidential identity here so that the recipient is not identifiable. + * @param deriveState a function to derive an output state based on an input state, amount for the output + * and public key to pay to. + * @param T A type representing a token + * @param S A fungible asset state type + * @return A [Pair] of the same transaction builder passed in as [tx], and the list of keys that need to sign + * the resulting transaction for it to be valid. + * @throws InsufficientBalanceException when a cash spending transaction fails because + * there is insufficient quantity for a given currency (and optionally set of Issuer Parties). + */ + @Throws(InsufficientBalanceException::class) + @JvmStatic + fun , T: Any> generateSpend(tx: TransactionBuilder, + payments: List>, + acceptableStates: List>, + payChangeTo: AbstractParty, + deriveState: (TransactionState, Amount>, AbstractParty) -> TransactionState, + generateMoveCommand: () -> CommandData): Pair> { + // Discussion + // + // This code is analogous to the Wallet.send() set of methods in bitcoinj, and has the same general outline. + // + // First we must select a set of asset states (which for convenience we will call 'coins' here, as in bitcoinj). + // The input states can be considered our "vault", and may consist of different products, and with different + // issuers and deposits. + // + // Coin selection is a complex problem all by itself and many different approaches can be used. It is easily + // possible for different actors to use different algorithms and approaches that, for example, compete on + // privacy vs efficiency (number of states created). Some spends may be artificial just for the purposes of + // obfuscation and so on. + // + // Having selected input states of the correct asset, we must craft output states for the amount we're sending and + // the "change", which goes back to us. The change is required to make the amounts balance. We may need more + // than one change output in order to avoid merging assets from different deposits. The point of this design + // is to ensure that ledger entries are immutable and globally identifiable. + // + // Finally, we add the states to the provided partial transaction. + + // TODO: We should be prepared to produce multiple transactions spending inputs from + // different notaries, or at least group states by notary and take the set with the + // highest total value. + + // TODO: Check that re-running this on the same transaction multiple times does the right thing. + + // The notary may be associated with a locked state only. + tx.notary = acceptableStates.firstOrNull()?.state?.notary + + // Calculate the total amount we're sending (they must be all of a compatible token). + val totalSendAmount = payments.map { it.amount }.sumOrThrow() + // Select a subset of the available states we were given that sums up to >= totalSendAmount. + val (gathered, gatheredAmount) = gatherCoins(acceptableStates, totalSendAmount) + check(gatheredAmount >= totalSendAmount) + val keysUsed = gathered.map { it.state.data.owner.owningKey } + + // Now calculate the output states. This is complicated by the fact that a single payment may require + // multiple output states, due to the need to keep states separated by issuer. We start by figuring out + // how much we've gathered for each issuer: this map will keep track of how much we've used from each + // as we work our way through the payments. + val statesGroupedByIssuer = gathered.groupBy { it.state.data.amount.token } + val remainingFromEachIssuer = statesGroupedByIssuer + .mapValues { + it.value.map { + it.state.data.amount + }.sumOrThrow() + }.toList().toMutableList() + val outputStates = mutableListOf>() + for ((party, paymentAmount) in payments) { + var remainingToPay = paymentAmount.quantity + while (remainingToPay > 0) { + val (token, remainingFromCurrentIssuer) = remainingFromEachIssuer.last() + val templateState = statesGroupedByIssuer[token]!!.first().state + val delta = remainingFromCurrentIssuer.quantity - remainingToPay + when { + delta > 0 -> { + // The states from the current issuer more than covers this payment. + outputStates += deriveState(templateState, Amount(remainingToPay, token), party) + remainingFromEachIssuer[0] = Pair(token, Amount(delta, token)) + remainingToPay = 0 + } + delta == 0L -> { + // The states from the current issuer exactly covers this payment. + outputStates += deriveState(templateState, Amount(remainingToPay, token), party) + remainingFromEachIssuer.removeAt(remainingFromEachIssuer.lastIndex) + remainingToPay = 0 + } + delta < 0 -> { + // The states from the current issuer don't cover this payment, so we'll have to use >1 output + // state to cover this payment. + outputStates += deriveState(templateState, remainingFromCurrentIssuer, party) + remainingFromEachIssuer.removeAt(remainingFromEachIssuer.lastIndex) + remainingToPay -= remainingFromCurrentIssuer.quantity + } + } + } + } + + // Whatever values we have left over for each issuer must become change outputs. + for ((token, amount) in remainingFromEachIssuer) { + val templateState = statesGroupedByIssuer[token]!!.first().state + outputStates += deriveState(templateState, amount, payChangeTo) + } + + for (state in gathered) tx.addInputState(state) + for (state in outputStates) tx.addOutputState(state) + + // What if we already have a move command with the right keys? Filter it out here or in platform code? + tx.addCommand(generateMoveCommand(), keysUsed) + + return Pair(tx, keysUsed) + } + + /** + * Gather assets from the given list of states, sufficient to match or exceed the given amount. + * + * @param acceptableCoins list of states to use as inputs. + * @param amount the amount to gather states up to. + * @throws InsufficientBalanceException if there isn't enough value in the states to cover the requested amount. + */ + @Throws(InsufficientBalanceException::class) + private fun , T : Any> gatherCoins(acceptableCoins: Collection>, + amount: Amount): Pair>, Amount> { + require(amount.quantity > 0) { "Cannot gather zero coins" } + val gathered = arrayListOf>() + var gatheredAmount = Amount(0, amount.token) + for (c in acceptableCoins) { + if (gatheredAmount >= amount) break + gathered.add(c) + gatheredAmount += Amount(c.state.data.amount.quantity, amount.token) + } + + if (gatheredAmount < amount) { + log.trace { "Insufficient balance: requested $amount, available $gatheredAmount" } + throw InsufficientBalanceException(amount - gatheredAmount) + } + + log.trace { "Gathered coins: requested $amount, available $gatheredAmount, change: ${gatheredAmount - amount}" } + + return Pair(gathered, gatheredAmount) + } + + /** + * Generate an transaction exiting fungible assets from the ledger. + * + * @param tx transaction builder to add states and commands to. + * @param amountIssued the amount to be exited, represented as a quantity of issued currency. + * @param assetStates the asset states to take funds from. No checks are done about ownership of these states, it is + * the responsibility of the caller to check that they do not attempt to exit funds held by others. + * @return the public keys which must sign the transaction for it to be valid. + */ + @Throws(InsufficientBalanceException::class) + @JvmStatic + fun , T: Any> generateExit(tx: TransactionBuilder, amountIssued: Amount>, + assetStates: List>, + deriveState: (TransactionState, Amount>, AbstractParty) -> TransactionState, + generateMoveCommand: () -> CommandData, + generateExitCommand: (Amount>) -> CommandData): Set { + val owner = assetStates.map { it.state.data.owner }.toSet().singleOrNull() ?: throw InsufficientBalanceException(amountIssued) + val currency = amountIssued.token.product + val amount = Amount(amountIssued.quantity, currency) + var acceptableCoins = assetStates.filter { ref -> ref.state.data.amount.token == amountIssued.token } + tx.notary = acceptableCoins.firstOrNull()?.state?.notary + // TODO: We should be prepared to produce multiple transactions exiting inputs from + // different notaries, or at least group states by notary and take the set with the + // highest total value + acceptableCoins = acceptableCoins.filter { it.state.notary == tx.notary } + + val (gathered, gatheredAmount) = gatherCoins(acceptableCoins, amount) + val takeChangeFrom = gathered.lastOrNull() + val change = if (takeChangeFrom != null && gatheredAmount > amount) { + Amount(gatheredAmount.quantity - amount.quantity, takeChangeFrom.state.data.amount.token) + } else { + null + } + + val outputs = if (change != null) { + // Add a change output and adjust the last output downwards. + listOf(deriveState(gathered.last().state, change, owner)) + } else emptyList() + + for (state in gathered) tx.addInputState(state) + for (state in outputs) tx.addOutputState(state) + val moveKeys = gathered.map { it.state.data.owner.owningKey } + val exitKeys = gathered.flatMap { it.state.data.exitKeys } + tx.addCommand(generateMoveCommand(), moveKeys) + tx.addCommand(generateExitCommand(amountIssued), exitKeys) + return (moveKeys + exitKeys).toSet() + } + + /** + * Puts together an issuance transaction for the specified state. Normally contracts will provide convenient + * wrappers around this function, which build the state for you, and those should be used in preference. + */ + @JvmStatic + fun , T: Any> generateIssue(tx: TransactionBuilder, + transactionState: TransactionState, + issueCommand: CommandData): Set { + check(tx.inputStates().isEmpty()) + check(tx.outputStates().map { it.data }.filterIsInstance(transactionState.javaClass).isEmpty()) + require(transactionState.data.amount.quantity > 0) + val at = transactionState.data.amount.token.issuer + val commandSigner = at.party.owningKey + tx.addOutputState(transactionState) + tx.addCommand(issueCommand, commandSigner) + return setOf(commandSigner) + } + } + + abstract fun extractCommands(commands: Collection>): Collection> + + /** + * Generate an transaction exiting assets from the ledger. + * + * @param tx transaction builder to add states and commands to. + * @param amountIssued the amount to be exited, represented as a quantity of issued currency. + * @param assetStates the asset states to take funds from. No checks are done about ownership of these states, it is + * the responsibility of the caller to check that they do not exit funds held by others. + * @return the public keys which must sign the transaction for it to be valid. + */ + @Throws(InsufficientBalanceException::class) + fun generateExit(tx: TransactionBuilder, amountIssued: Amount>, + assetStates: List>): Set { + return generateExit( + tx, + amountIssued, + assetStates, + deriveState = { state, amount, owner -> deriveState(state, amount, owner) }, + generateMoveCommand = { -> generateMoveCommand() }, + generateExitCommand = { amount -> generateExitCommand(amount) } + ) + } + + abstract fun generateExitCommand(amount: Amount>): CommandData + abstract fun generateMoveCommand(): MoveCommand + + /** + * Derive a new transaction state based on the given example, with amount and owner modified. This allows concrete + * implementations to have fields in their state which we don't know about here, and we simply leave them untouched + * when sending out "change" from spending/exiting. + */ + abstract fun deriveState(txState: TransactionState, amount: Amount>, owner: AbstractParty): TransactionState +} diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCashSchemaV1.kt b/perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCashSchemaV1.kt new file mode 100644 index 0000000000..bf4f46c805 --- /dev/null +++ b/perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCashSchemaV1.kt @@ -0,0 +1,45 @@ +package net.corda.ptflows.schemas + + +import net.corda.core.identity.AbstractParty +import net.corda.core.schemas.MappedSchema +import net.corda.core.schemas.PersistentState +import net.corda.core.serialization.CordaSerializable +import javax.persistence.Column +import javax.persistence.Entity +import javax.persistence.Index +import javax.persistence.Table + +/** + * An object used to fully qualify the [CashSchema] family name (i.e. independent of version). + */ +object PtCashSchema + +/** + * First version of a cash contract ORM schema that maps all fields of the [Cash] contract state as it stood + * at the time of writing. + */ +@CordaSerializable +object PtCashSchemaV1 : MappedSchema(schemaFamily = PtCashSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCashState::class.java)) { + @Entity + @Table(name = "contract_cash_states", + indexes = arrayOf(Index(name = "ccy_code_idx", columnList = "ccy_code"), + Index(name = "pennies_idx", columnList = "pennies"))) + class PersistentCashState( + /** X500Name of owner party **/ + @Column(name = "owner_name") + var owner: AbstractParty, + + @Column(name = "pennies") + var pennies: Long, + + @Column(name = "ccy_code", length = 3) + var currency: String, + + @Column(name = "issuer_key") + var issuerParty: String, + + @Column(name = "issuer_ref") + var issuerRef: ByteArray + ) : PersistentState() +} diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCommercialPaperSchemaV1.kt b/perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCommercialPaperSchemaV1.kt new file mode 100644 index 0000000000..3fc657d1a1 --- /dev/null +++ b/perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCommercialPaperSchemaV1.kt @@ -0,0 +1,52 @@ +package net.corda.ptflows.schemas +import net.corda.core.schemas.MappedSchema +import net.corda.core.schemas.PersistentState +import net.corda.core.serialization.CordaSerializable +import java.time.Instant +import javax.persistence.Column +import javax.persistence.Entity +import javax.persistence.Index +import javax.persistence.Table + +/** + * An object used to fully qualify the [CommercialPaperSchema] family name (i.e. independent of version). + */ +object PtCommercialPaperSchema + +/** + * First version of a commercial paper contract ORM schema that maps all fields of the [CommercialPaper] contract state + * as it stood at the time of writing. + */ +@CordaSerializable +object PtCommercialPaperSchemaV1 : MappedSchema(schemaFamily = PtCommercialPaperSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCommercialPaperState::class.java)) { + @Entity + @Table(name = "cp_states", + indexes = arrayOf(Index(name = "ccy_code_index", columnList = "ccy_code"), + Index(name = "maturity_index", columnList = "maturity_instant"), + Index(name = "face_value_index", columnList = "face_value"))) + class PersistentCommercialPaperState( + @Column(name = "issuance_key") + var issuanceParty: String, + + @Column(name = "issuance_ref") + var issuanceRef: ByteArray, + + @Column(name = "owner_key") + var owner: String, + + @Column(name = "maturity_instant") + var maturity: Instant, + + @Column(name = "face_value") + var faceValue: Long, + + @Column(name = "ccy_code", length = 3) + var currency: String, + + @Column(name = "face_value_issuer_key") + var faceValueIssuerParty: String, + + @Column(name = "face_value_issuer_ref") + var faceValueIssuerRef: ByteArray + ) : PersistentState() +} diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/utils/StateSummingUtilities.kt b/perftestflows/src/main/kotlin/net/corda/ptflows/utils/StateSummingUtilities.kt new file mode 100644 index 0000000000..66ed583626 --- /dev/null +++ b/perftestflows/src/main/kotlin/net/corda/ptflows/utils/StateSummingUtilities.kt @@ -0,0 +1,39 @@ +package net.corda.ptflows.utils + +import net.corda.core.contracts.Amount +import net.corda.core.contracts.Amount.Companion.sumOrNull +import net.corda.core.contracts.Amount.Companion.sumOrThrow +import net.corda.core.contracts.Amount.Companion.sumOrZero +import net.corda.core.contracts.ContractState +import net.corda.core.contracts.FungibleAsset +import net.corda.core.contracts.Issued +import net.corda.core.identity.AbstractParty +import java.util.* + +/** + * Sums the cash states in the list belonging to a single owner, throwing an exception + * if there are none, or if any of the cash states cannot be added together (i.e. are + * different currencies or issuers). + */ +fun Iterable.sumCashBy(owner: AbstractParty): Amount> = filterIsInstance().filter { it.owner == owner }.map { it.amount }.sumOrThrow() + +/** + * Sums the cash states in the list, throwing an exception if there are none, or if any of the cash + * states cannot be added together (i.e. are different currencies or issuers). + */ +fun Iterable.sumCash(): Amount> = filterIsInstance().map { it.amount }.sumOrThrow() + +/** Sums the cash states in the list, returning null if there are none. */ +fun Iterable.sumCashOrNull(): Amount>? = filterIsInstance().map { it.amount }.sumOrNull() + +/** Sums the cash states in the list, returning zero of the given currency+issuer if there are none. */ +fun Iterable.sumCashOrZero(currency: Issued): Amount> { + return filterIsInstance().map { it.amount }.sumOrZero(currency) +} + +/** Sums the asset states in the list, returning null if there are none. */ +fun Iterable.sumFungibleOrNull() = filterIsInstance>().map { it.amount }.sumOrNull() + +/** Sums the asset states in the list, returning zero of the given token if there are none. */ +fun Iterable.sumFungibleOrZero(token: Issued) = filterIsInstance>().map { it.amount }.sumOrZero(token) + diff --git a/settings.gradle b/settings.gradle index 9bdb3b1ad1..eabb775ac6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -46,4 +46,5 @@ include 'doorman' include 'verify-enclave' include 'sgx-jvm/hsm-tool' include 'signing-server' +include 'perftestflows' From 22bf2b1c1d6c0122a8a9e99eb2444e0ce5a69188 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Tue, 10 Oct 2017 14:32:18 +0100 Subject: [PATCH 02/15] PtCashTests and missing bits of implementation to make them work --- .../kotlin/net/corda/ptflows/PtCurrencies.kt | 34 + .../corda/ptflows/contracts/asset/PtCash.kt | 4 +- .../cash/selection/PtCashSelectionH2Impl.kt | 151 +++ ...da.ptflows.contracts.asset.PtCashSelection | 2 + .../contracts/LondonHolidayCalendar.txt | 1 + .../contracts/NewYorkHolidayCalendar.txt | 1 + .../main/resources/finance/utils/cities.txt | 1002 +++++++++++++++++ .../ptflows/contract/asset/PtCashTests.kt | 896 +++++++++++++++ 8 files changed, 2089 insertions(+), 2 deletions(-) create mode 100644 perftestflows/src/main/kotlin/net/corda/ptflows/PtCurrencies.kt create mode 100644 perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/cash/selection/PtCashSelectionH2Impl.kt create mode 100644 perftestflows/src/main/resources/META-INF/services/net.corda.ptflows.contracts.asset.PtCashSelection create mode 100644 perftestflows/src/main/resources/finance/contracts/LondonHolidayCalendar.txt create mode 100644 perftestflows/src/main/resources/finance/contracts/NewYorkHolidayCalendar.txt create mode 100644 perftestflows/src/main/resources/finance/utils/cities.txt create mode 100644 perftestflows/src/test/kotlin/net/corda/ptflows/contract/asset/PtCashTests.kt diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/PtCurrencies.kt b/perftestflows/src/main/kotlin/net/corda/ptflows/PtCurrencies.kt new file mode 100644 index 0000000000..c957b5ce16 --- /dev/null +++ b/perftestflows/src/main/kotlin/net/corda/ptflows/PtCurrencies.kt @@ -0,0 +1,34 @@ +@file:JvmName("PtCurrencies") + +package net.corda.ptflows + +import net.corda.core.contracts.Amount +import net.corda.core.contracts.Issued +import net.corda.core.contracts.PartyAndReference +import java.math.BigDecimal +import java.util.* + +@JvmField val USD: Currency = Currency.getInstance("USD") +@JvmField val GBP: Currency = Currency.getInstance("GBP") +@JvmField val EUR: Currency = Currency.getInstance("EUR") +@JvmField val CHF: Currency = Currency.getInstance("CHF") +@JvmField val JPY: Currency = Currency.getInstance("JPY") +@JvmField val RUB: Currency = Currency.getInstance("RUB") + +fun AMOUNT(amount: Int, token: T): Amount = Amount.fromDecimal(BigDecimal.valueOf(amount.toLong()), token) +fun AMOUNT(amount: Double, token: T): Amount = Amount.fromDecimal(BigDecimal.valueOf(amount), token) +fun DOLLARS(amount: Int): Amount = AMOUNT(amount, USD) +fun DOLLARS(amount: Double): Amount = AMOUNT(amount, USD) +fun POUNDS(amount: Int): Amount = AMOUNT(amount, GBP) +fun SWISS_FRANCS(amount: Int): Amount = AMOUNT(amount, CHF) + +val Int.DOLLARS: Amount get() = DOLLARS(this) +val Double.DOLLARS: Amount get() = DOLLARS(this) +val Int.POUNDS: Amount get() = POUNDS(this) +val Int.SWISS_FRANCS: Amount get() = SWISS_FRANCS(this) + +infix fun Currency.`issued by`(deposit: PartyAndReference) = issuedBy(deposit) +infix fun Amount.`issued by`(deposit: PartyAndReference) = issuedBy(deposit) +infix fun Currency.issuedBy(deposit: PartyAndReference) = Issued(deposit, this) +infix fun Amount.issuedBy(deposit: PartyAndReference) = Amount(quantity, displayTokenSize, token.issuedBy(deposit)) + diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/PtCash.kt b/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/PtCash.kt index 768a17208a..45a5e3adc4 100644 --- a/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/PtCash.kt +++ b/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/PtCash.kt @@ -53,7 +53,7 @@ interface PtCashSelection { instance.set(cashSelectionAlgo) cashSelectionAlgo } ?: throw ClassNotFoundException("\nUnable to load compatible cash selection algorithm implementation for JDBC driver ($_metadata)." + - "\nPlease specify an implementation in META-INF/services/net.corda.finance.contracts.asset.CashSelection") + "\nPlease specify an implementation in META-INF/services/net.corda.ptflows.contracts.asset.PtCashSelection") }.invoke() } } @@ -261,7 +261,7 @@ class PtCash : PtOnLedgerAsset() { } companion object { - const val PROGRAM_ID: ContractClassName = "net.corda.finance.contracts.asset.Cash" + const val PROGRAM_ID: ContractClassName = "net.corda.ptflows.contracts.asset.PtCash" /** * Generate a transaction that moves an amount of currency to the given party, and sends any change back to diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/cash/selection/PtCashSelectionH2Impl.kt b/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/cash/selection/PtCashSelectionH2Impl.kt new file mode 100644 index 0000000000..fbc8993b26 --- /dev/null +++ b/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/cash/selection/PtCashSelectionH2Impl.kt @@ -0,0 +1,151 @@ +package net.corda.ptflows.contracts.asset.cash.selection + + +import co.paralleluniverse.fibers.Suspendable +import co.paralleluniverse.strands.Strand +import net.corda.core.contracts.Amount +import net.corda.core.contracts.StateAndRef +import net.corda.core.contracts.StateRef +import net.corda.core.contracts.TransactionState +import net.corda.core.crypto.SecureHash +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.Party +import net.corda.core.node.ServiceHub +import net.corda.core.node.services.StatesNotAvailableException +import net.corda.core.serialization.SerializationDefaults +import net.corda.core.serialization.deserialize +import net.corda.core.utilities.* +import net.corda.ptflows.contracts.asset.PtCash +import net.corda.ptflows.contracts.asset.PtCashSelection +import java.sql.DatabaseMetaData +import java.sql.SQLException +import java.util.* +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + +class PtCashSelectionH2Impl : PtCashSelection { + + companion object { + const val JDBC_DRIVER_NAME = "H2 JDBC Driver" + val log = loggerFor() + } + + override fun isCompatible(metadata: DatabaseMetaData): Boolean { + return metadata.driverName == JDBC_DRIVER_NAME + } + + // coin selection retry loop counter, sleep (msecs) and lock for selecting states + private val MAX_RETRIES = 5 + private val RETRY_SLEEP = 100 + private val spendLock: ReentrantLock = ReentrantLock() + + /** + * An optimised query to gather Cash states that are available and retry if they are temporarily unavailable. + * @param services The service hub to allow access to the database session + * @param amount The amount of currency desired (ignoring issues, but specifying the currency) + * @param onlyFromIssuerParties If empty the operation ignores the specifics of the issuer, + * otherwise the set of eligible states wil be filtered to only include those from these issuers. + * @param notary If null the notary source is ignored, if specified then only states marked + * with this notary are included. + * @param lockId The FlowLogic.runId.uuid of the flow, which is used to soft reserve the states. + * Also, previous outputs of the flow will be eligible as they are implicitly locked with this id until the flow completes. + * @param withIssuerRefs If not empty the specific set of issuer references to match against. + * @return The matching states that were found. If sufficient funds were found these will be locked, + * otherwise what is available is returned unlocked for informational purposes. + */ + @Suspendable + override fun unconsumedCashStatesForSpending(services: ServiceHub, + amount: Amount, + onlyFromIssuerParties: Set, + notary: Party?, + lockId: UUID, + withIssuerRefs: Set): List> { + + val issuerKeysStr = onlyFromIssuerParties.fold("") { left, right -> left + "('${right.owningKey.toBase58String()}')," }.dropLast(1) + val issuerRefsStr = withIssuerRefs.fold("") { left, right -> left + "('${right.bytes.toHexString()}')," }.dropLast(1) + + val stateAndRefs = mutableListOf>() + + // We are using an H2 specific means of selecting a minimum set of rows that match a request amount of coins: + // 1) There is no standard SQL mechanism of calculating a cumulative total on a field and restricting row selection on the + // running total of such an accumulator + // 2) H2 uses session variables to perform this accumulator function: + // http://www.h2database.com/html/functions.html#set + // 3) H2 does not support JOIN's in FOR UPDATE (hence we are forced to execute 2 queries) + + for (retryCount in 1..MAX_RETRIES) { + + spendLock.withLock { + val statement = services.jdbcSession().createStatement() + try { + statement.execute("CALL SET(@t, 0);") + + // we select spendable states irrespective of lock but prioritised by unlocked ones (Eg. null) + // the softLockReserve update will detect whether we try to lock states locked by others + val selectJoin = """ + SELECT vs.transaction_id, vs.output_index, vs.contract_state, ccs.pennies, SET(@t, ifnull(@t,0)+ccs.pennies) total_pennies, vs.lock_id + FROM vault_states AS vs, contract_cash_states AS ccs + WHERE vs.transaction_id = ccs.transaction_id AND vs.output_index = ccs.output_index + AND vs.state_status = 0 + AND ccs.ccy_code = '${amount.token}' and @t < ${amount.quantity} + AND (vs.lock_id = '$lockId' OR vs.lock_id is null) + """ + + (if (notary != null) + " AND vs.notary_name = '${notary.name}'" else "") + + (if (onlyFromIssuerParties.isNotEmpty()) + " AND ccs.issuer_key IN ($issuerKeysStr)" else "") + + (if (withIssuerRefs.isNotEmpty()) + " AND ccs.issuer_ref IN ($issuerRefsStr)" else "") + + // Retrieve spendable state refs + val rs = statement.executeQuery(selectJoin) + stateAndRefs.clear() + log.debug(selectJoin) + var totalPennies = 0L + while (rs.next()) { + val txHash = SecureHash.parse(rs.getString(1)) + val index = rs.getInt(2) + val stateRef = StateRef(txHash, index) + val state = rs.getBytes(3).deserialize>(context = SerializationDefaults.STORAGE_CONTEXT) + val pennies = rs.getLong(4) + totalPennies = rs.getLong(5) + val rowLockId = rs.getString(6) + stateAndRefs.add(StateAndRef(state, stateRef)) + log.trace { "ROW: $rowLockId ($lockId): $stateRef : $pennies ($totalPennies)" } + } + + if (stateAndRefs.isNotEmpty() && totalPennies >= amount.quantity) { + // we should have a minimum number of states to satisfy our selection `amount` criteria + log.trace("Coin selection for $amount retrieved ${stateAndRefs.count()} states totalling $totalPennies pennies: $stateAndRefs") + + // With the current single threaded state machine available states are guaranteed to lock. + // TODO However, we will have to revisit these methods in the future multi-threaded. + services.vaultService.softLockReserve(lockId, (stateAndRefs.map { it.ref }).toNonEmptySet()) + return stateAndRefs + } + log.trace("Coin selection requested $amount but retrieved $totalPennies pennies with state refs: ${stateAndRefs.map { it.ref }}") + // retry as more states may become available + } catch (e: SQLException) { + log.error("""Failed retrieving unconsumed states for: amount [$amount], onlyFromIssuerParties [$onlyFromIssuerParties], notary [$notary], lockId [$lockId] + $e. + """) + } catch (e: StatesNotAvailableException) { // Should never happen with single threaded state machine + stateAndRefs.clear() + log.warn(e.message) + // retry only if there are locked states that may become available again (or consumed with change) + } finally { + statement.close() + } + } + + log.warn("Coin selection failed on attempt $retryCount") + // TODO: revisit the back off strategy for contended spending. + if (retryCount != MAX_RETRIES) { + Strand.sleep(RETRY_SLEEP * retryCount.toLong()) + } + } + + log.warn("Insufficient spendable states identified for $amount") + return stateAndRefs + } +} \ No newline at end of file diff --git a/perftestflows/src/main/resources/META-INF/services/net.corda.ptflows.contracts.asset.PtCashSelection b/perftestflows/src/main/resources/META-INF/services/net.corda.ptflows.contracts.asset.PtCashSelection new file mode 100644 index 0000000000..c60a063a74 --- /dev/null +++ b/perftestflows/src/main/resources/META-INF/services/net.corda.ptflows.contracts.asset.PtCashSelection @@ -0,0 +1,2 @@ +net.corda.ptflows.contracts.asset.cash.selection.PtCashSelectionH2Impl + diff --git a/perftestflows/src/main/resources/finance/contracts/LondonHolidayCalendar.txt b/perftestflows/src/main/resources/finance/contracts/LondonHolidayCalendar.txt new file mode 100644 index 0000000000..a68eaa7b30 --- /dev/null +++ b/perftestflows/src/main/resources/finance/contracts/LondonHolidayCalendar.txt @@ -0,0 +1 @@ +2015-01-01,2015-04-03,2015-04-06,2015-05-04,2015-05-25,2015-08-31,2015-12-25,2015-12-28,2016-01-01,2016-03-25,2016-03-28,2016-05-02,2016-05-30,2016-08-29,2016-12-26,2016-12-27,2017-01-02,2017-04-14,2017-04-17,2017-05-01,2017-05-29,2017-08-28,2017-12-25,2017-12-26,2018-01-01,2018-03-30,2018-04-02,2018-05-07,2018-05-28,2018-08-27,2018-12-25,2018-12-26,2019-01-01,2019-04-19,2019-04-22,2019-05-06,2019-05-27,2019-08-26,2019-12-25,2019-12-26,2020-01-01,2020-04-10,2020-04-13,2020-05-04,2020-05-25,2020-08-31,2020-12-25,2020-12-28,2021-01-01,2021-04-02,2021-04-05,2021-05-03,2021-05-31,2021-08-30,2021-12-27,2021-12-28,2022-01-03,2022-04-15,2022-04-18,2022-05-02,2022-05-30,2022-08-29,2022-12-26,2022-12-27,2023-01-02,2023-04-07,2023-04-10,2023-05-01,2023-05-29,2023-08-28,2023-12-25,2023-12-26,2024-01-01,2024-03-29,2024-04-01,2024-05-06,2024-05-27,2024-08-26,2024-12-25,2024-12-26 \ No newline at end of file diff --git a/perftestflows/src/main/resources/finance/contracts/NewYorkHolidayCalendar.txt b/perftestflows/src/main/resources/finance/contracts/NewYorkHolidayCalendar.txt new file mode 100644 index 0000000000..e2edfa6902 --- /dev/null +++ b/perftestflows/src/main/resources/finance/contracts/NewYorkHolidayCalendar.txt @@ -0,0 +1 @@ +2015-01-01,2015-01-19,2015-02-16,2015-02-18,2015-05-25,2015-07-03,2015-09-07,2015-10-12,2015-11-11,2015-11-26,2015-12-25,2016-01-01,2016-01-18,2016-02-10,2016-02-15,2016-05-30,2016-07-04,2016-09-05,2016-10-10,2016-11-11,2016-11-24,2016-12-26,2017-01-02,2017-01-16,2017-02-20,2017-03-01,2017-05-29,2017-07-04,2017-09-04,2017-10-09,2017-11-10,2017-11-23,2017-12-25,2018-01-01,2018-01-15,2018-02-14,2018-02-19,2018-05-28,2018-07-04,2018-09-03,2018-10-08,2018-11-12,2018-11-22,2018-12-25,2019-01-01,2019-01-21,2019-02-18,2019-03-06,2019-05-27,2019-07-04,2019-09-02,2019-10-14,2019-11-11,2019-11-28,2019-12-25,2020-01-01,2020-01-20,2020-02-17,2020-02-26,2020-05-25,2020-07-03,2020-09-07,2020-10-12,2020-11-11,2020-11-26,2020-12-25,2021-01-01,2021-01-18,2021-02-15,2021-02-17,2021-05-31,2021-07-05,2021-09-06,2021-10-11,2021-11-11,2021-11-25,2021-12-24,2022-01-17,2022-02-21,2022-03-02,2022-05-30,2022-07-04,2022-09-05,2022-10-10,2022-11-11,2022-11-24,2022-12-26,2023-01-02,2023-01-16,2023-02-20,2023-02-22,2023-05-29,2023-07-04,2023-09-04,2023-10-09,2023-11-10,2023-11-23,2023-12-25,2024-01-01,2024-01-15,2024-02-14,2024-02-19,2024-05-27,2024-07-04,2024-09-02,2024-10-14,2024-11-11,2024-11-28,2024-12-25 \ No newline at end of file diff --git a/perftestflows/src/main/resources/finance/utils/cities.txt b/perftestflows/src/main/resources/finance/utils/cities.txt new file mode 100644 index 0000000000..baf4c4f814 --- /dev/null +++ b/perftestflows/src/main/resources/finance/utils/cities.txt @@ -0,0 +1,1002 @@ +# name longitude latitude +Tokyo (JP) 139.75 35.68 +Beijing Shi (CN) 116.38 39.92 +Shanghai (CN) 121.4 31 +Buenos Aires (AR) -58.67 -34.58 +Bombay (IN) 72.82 18.97 +Karachi (PK) 67.03 24.87 +Constantinople (TR) 28.95 41.02 +Mexico (MX) -99.03 19.38 +Delhi (IN) 77.22 28.67 +Manila (PH) 120.97 14.6 +Moscow (RU) 37.6 55.75 +Seoul (KR) 127 37.55 +Sao Paulo (BR) -46.62 -23.52 +Istanbul (TR) 28.95 41.02 +Lagos (NG) 3.38 6.45 +Mexico (MX) -99.13 19.43 +Jakarta (ID) 106.82 -6.17 +Edo (JP) 139.75 35.68 +New York (US) -74 40.7 +Kinshasa (CD) 15.3 -4.28 +Al Qahirah (EG) 31.25 30.05 +Lima (PE) -77.03 -12.05 +Peking (CN) 116.38 39.92 +London (GB) -0.12 51.5 +Tehran (IR) 51.42 35.67 +Bogota (CO) -74.07 4.58 +Hong Kong (HK) 114.15 22.27 +Dhaka (BD) 90.4 23.72 +Lahore (PK) 74.33 31.53 +Rio De Janeiro (BR) -43.22 -22.88 +Baghdad (IQ) 44.38 33.33 +Bangkok (TH) 100.52 13.75 +Bangalore (IN) 77.57 12.97 +Santiago (CL) -70.67 -33.45 +Calcutta (IN) 88.37 22.57 +Toronto (CA) -79.42 43.67 +Dagon (MM) 96.17 16.77 +Sydney (AU) 151.22 -33.87 +Madras (IN) 80.27 13.07 +Abia (NG) 7.87 5.52 +Wuhan (CN) 114.27 30.57 +Ogun (NG) 3.6 7.22 +Saint Petersburg (RU) 30.25 59.88 +Chongqing (CN) 106.55 29.55 +Xian (CN) 108.93 34.25 +Chengdu (CN) 104.07 30.67 +Chittagong (BD) 91.8 22.35 +Los Angeles (US) -118.23 34.05 +Alexandria (EG) 29.92 31.18 +Tianjin (CN) 117.17 39.13 +Melbourne (AU) 144.97 -37.82 +Ahmadabad (IN) 72.62 23.02 +Abidjan (CI) -4.02 5.33 +Busan (KR) 129.03 35.1 +Kano (NG) 8.52 11.98 +Casablanca (MA) -7.6 33.58 +Hyderabad (IN) 78.47 17.37 +Yokohama (JP) 139.65 35.45 +Ibadan (NG) 3.88 7.38 +Singapore (SG) 103.85 1.28 +Ankara (TR) 32.85 39.92 +Shenyang (CN) 123.42 41.78 +Riyadh (SA) 46.77 24.63 +Ho Chi Minh City (VN) 106.67 10.75 +Cape Town (ZA) 18.42 -33.92 +Berlin (DE) 13.4 52.52 +Montreal (CA) -73.57 45.5 +Harbin (CN) 126.65 45.75 +Pyongyang (KP) 125.75 39.02 +Guangzhou (CN) 113.25 23.12 +Durban (ZA) 31.02 -29.85 +Madrid (ES) -3.67 40.38 +Nanjing (CN) 118.77 32.05 +Kabul (AF) 69.17 34.52 +Pune (IN) 73.87 18.52 +Surat (IN) 72.9 20.97 +Chicago (US) -87.65 41.85 +Kanpur (IN) 80.33 26.47 +Umm Durman (SD) 32.43 15.63 +Luanda (AO) 13.23 -8.83 +Addis Abeba (ET) 38.7 9.02 +Nairobi (KE) 36.82 -1.27 +Taiyuan (CN) 112.47 37.72 +Jaipur (IN) 75.82 26.92 +Salvador (BR) -38.52 -12.97 +Dakar (SN) -17.43 14.67 +Dar Es Salaam (TZ) 39.27 -6.78 +Rome (IT) 12.47 41.88 +Yunfu (CN) 112.02 22.92 +Changwat Nakhon Rat Sima (TH) 102.12 14.97 +Al Basrah (IQ) 47.82 30.48 +Osaka (JP) 135.5 34.67 +Mogadishu (SO) 45.37 2.07 +Taegu (KR) 128.58 35.87 +Jiddah (SA) 39.22 21.52 +Changchun (CN) 125.32 43.87 +Taipei (TW) 121.52 25.03 +Kiev (UA) 30.52 50.42 +Faisalabad (PK) 73.07 31.42 +Izmir (TR) 27.15 38.4 +Lakhnau (IN) 80.92 26.85 +Konia (TR) 32.47 37.85 +Gizeh (EG) 31.2 30 +Ceara (BR) -38.5 -3.72 +Cali (CO) -76.52 3.43 +Surabaya (ID) 112.75 -7.23 +Brusa (TR) 29.05 40.18 +Belo Horizonte (BR) -43.92 -19.92 +Grand Dakar (SN) -17.45 14.7 +Al Kuwait (KW) 47.97 29.37 +Mashhad (IR) 59.6 36.28 +Brooklyn (US) -73.95 40.63 +Quezon (PH) 121.03 14.63 +Nagpur (IN) 79.08 21.13 +Harare (ZW) 31.03 -17.82 +Brasilia (BR) -47.92 -15.77 +Santo Domingo (DO) -69.9 18.47 +Nagoya (JP) 136.92 35.17 +Havana (CU) -82.35 23.12 +Kosovo (RS) 21.08 42.63 +Aleppo (SY) 37.15 36.2 +Paris (FR) 2.32 48.87 +Changsha (CN) 113.1 28.17 +Jinan (CN) 116.98 36.67 +Al Mawsil Al Jadidah (IQ) 43.08 36.32 +Tangshan (CN) 114.7 37.32 +Dalian (CN) 121.6 38.9 +Houston (US) -95.35 29.75 +Johannesburg (ZA) 28.07 -26.18 +Kowloon (HK) 114.17 22.32 +Al Basrah Al Qadimah (IQ) 47.82 30.48 +Zhengzhou (CN) 113.52 34.68 +Cheng (CN) 113.63 34.75 +Medellin (CO) -75.53 6.28 +Algiers (DZ) 3.05 36.75 +Tashkent (UZ) 69.25 41.32 +Al Jazair (DZ) 3.05 36.75 +Khartoum (SD) 32.53 15.58 +Accra (GH) -0.22 5.53 +Guayaquil (EC) -79.9 -2.17 +Maracaibo (VE) -71.63 10.62 +Rabat (MA) -6.83 34.02 +Sapporo (JP) 141.35 43.05 +Jilin (CN) 126.55 43.85 +Hangzhou (CN) 120.17 30.25 +Bucuresti (RO) 26.1 44.42 +Nanchang (CN) 115.92 28.55 +Camayenne (GN) -13.68 9.53 +Brisbane (AU) 153.02 -27.5 +Vancouver (CA) -123.12 49.25 +Indore (IN) 75.82 22.72 +Caracas (VE) -66.92 10.5 +Ecatepec (MX) -99.05 19.6 +Sanaa (YE) 44.2 15.35 +Changwat Chiang Mai (TH) 98.97 18.78 +Medan (ID) 98.67 3.57 +Rawalpindi (PK) 73.07 33.6 +Minsk (BY) 27.57 53.88 +Mosul (IQ) 43.12 36.33 +Hamburg (DE) 10 53.53 +Curitiba (BR) -49.25 -25.42 +Budapest (HU) 19.07 47.5 +Bandung (ID) 107.62 -6.9 +Soweto (ZA) 27.87 -26.27 +Edessa (TR) 38.78 37.15 +Warsaw (PL) 21 52.25 +Qingdao (CN) 120.37 36.08 +Guadalajara (MX) -103.32 20.67 +Pretoria (ZA) 28.22 -25.7 +Alep (SY) 37.15 36.2 +Patna (IN) 85.12 25.6 +Bhopal (IN) 77.4 23.27 +New Patna (IN) 85.12 25.6 +Manaus (BR) -60.02 -3.1 +Xinyang (CN) 114.12 32.08 +Kaduna (NG) 7.43 10.52 +Damascus (SY) 36.28 33.5 +Phnom Penh (KH) 104.92 11.55 +Barcelona (ES) 2.17 41.37 +Al-sham (SY) 36.28 33.5 +Wien (AT) 16.37 48.2 +Esfahan (IR) 51.67 32.65 +Ludhiana (IN) 75.83 30.88 +Changwat Nakhon Si Thammarat (TH) 99.97 8.42 +Kobe (JP) 135.17 34.67 +Bekasi (ID) 106.98 -6.23 +Kaohsiung (TW) 120.33 22.62 +Kaohsiung (TW) 120.3 22.62 +Ciudad Juarez (MX) -106.47 31.72 +Urumqi (CN) 87.57 43.78 +Changwat Chiang Rai (TH) 99.75 19.87 +Changwat Surin (TH) 103.42 14.82 +Thana (IN) 72.97 19.18 +Recife (BR) -34.88 -8.05 +Taejon (KR) 127.32 36.35 +Daejeon (KR) 127.42 36.32 +Kumasi (GH) -1.62 6.67 +Kyoto (JP) 135.75 35 +Kuala Lumpur (MY) 101.7 3.17 +Philadelphia (US) -75.15 39.95 +Karaj (IR) 51 35.82 +Perth (AU) 115.82 -31.92 +Cordoba (AR) -64.17 -31.38 +Multan (PK) 71.47 30.18 +Ha Noi (VN) 105.83 21.02 +Kharkiv (UA) 36.25 50 +Agra (IN) 78.02 27.17 +Phoenix (US) -112.07 33.43 +Tabriz (IR) 46.28 38.07 +Novosibirsk (RU) 82.93 55.03 +Lanzhou (CN) 103.78 36.05 +Kwangju (KR) 126.9 35.15 +Bursa (TR) 29.05 40.18 +Changwat Roi Et (TH) 103.67 16.05 +Vadodara (IN) 73.2 22.3 +Belem (BR) -48.47 -1.43 +Juarez (MX) -106.47 31.72 +Fushun (CN) 123.87 41.7 +Quito (EC) -78.5 -0.22 +Fukuoka (JP) 130.4 33.57 +Puebla (MX) -98.2 19.05 +Antananarivo (MG) 47.52 -18.92 +Luoyang (CN) 112.45 34.68 +Hefei (CN) 117.27 31.85 +Hyderabad (PK) 68.37 25.37 +Valencia (VE) -68 10.17 +Gujranwala (PK) 74.17 32.13 +Barranquilla (CO) -74.78 10.95 +Tijuana (MX) -117.02 32.52 +Lubumbashi (CD) 27.47 -11.67 +Porto Alegre (BR) -51.2 -30.02 +Tangerang (ID) 106.62 -6.17 +Santa Cruz De La Sierra (BO) -63.17 -17.8 +Handan (CN) 114.52 36.57 +Kampala (UG) 32.55 0.3 +Kocaeli (TR) 29.92 40.77 +Suzhou (CN) 120.62 31.3 +Khulna (BD) 89.53 22.8 +Douala (CM) 9.68 4.05 +Shantou (CN) 116.67 23.37 +Gorakhpur (IN) 75.67 29.43 +Makasar (ID) 119.43 -5.13 +Kawasaki (JP) 139.72 35.52 +Kawasaki (JP) 139.52 35.87 +Montevideo (UY) -56.17 -34.85 +Baotou Shi (CN) 109.82 40.65 +Medina (SA) 39.6 24.47 +Yaounde (CM) 11.52 3.87 +Bamako (ML) -8 12.65 +Changwat Songkhla (TH) 100.52 7.12 +Nasik (IN) 73.78 19.97 +Changwat Chon Buri (TH) 100.97 13.37 +Semarang (ID) 110.42 -6.97 +Yekaterinburg (RU) 60.6 56.85 +San Diego (US) -117.15 32.7 +Pimpri (IN) 73.78 18.62 +Nizhniy Novgorod (RU) 44 56.32 +Faridabad (IN) 77.32 28.42 +Davao City (PH) 125.6 7.07 +Amman (JO) 35.92 31.93 +Ciudad De Montevideo (UY) -56.17 -34.85 +Lusaka (ZM) 28.27 -15.42 +Katmandu (NP) 85.32 27.72 +Kalyan (IN) 73.15 19.25 +Thane (IN) 72.97 19.18 +San Antonio (US) -98.48 29.42 +Stockholm (SE) 18.05 59.32 +Beirut (LB) 35.5 33.87 +Shiraz (IR) 52.53 29.6 +Adana (TR) 35.32 37 +Munich (DE) 11.57 48.13 +Suwon (KR) 127.02 37.28 +Suigen (KR) 127 37.55 +Palembang (ID) 104.75 -2.92 +Port-au-prince (HT) -72.33 18.53 +Nezahualcoyotl (MX) -99.02 19.4 +Meerut (IN) 77.7 28.97 +Peshawar (PK) 71.57 34 +Rosario (AR) -60.65 -32.95 +Central (PH) 125.6 7.07 +Central (PH) 125.42 6.82 +Jaizan (SA) 42.55 16.88 +Davao (PH) 125.6 7.07 +Dallas (US) -96.78 32.77 +Mandalay (MM) 96.07 22 +Almaty (KZ) 76.95 43.25 +Spetsgorodok (KZ) 76.88 43.33 +Bronx (US) -73.87 40.85 +Changwat Sakon Nakhon (TH) 104.03 17.07 +Omdurman (SD) 32.43 15.63 +Mecca (SA) 39.82 21.42 +Ghaziabad (IN) 77.42 28.67 +Anshan (CN) 122.98 41.12 +Xuzhou (CN) 117.18 34.27 +Depok (ID) 106.82 -6.4 +Maputo (MZ) 32.58 -25.95 +Freetown (SL) -13.23 8.48 +Changwat Chaiyaphum (TH) 102.02 15.8 +Fuzhou (CN) 119.3 26.05 +Changwat Nakhon Sawan (TH) 100.12 15.67 +Rajkot (IN) 70.77 22.3 +Pest (HU) 19.07 47.5 +Guiyang (CN) 106.72 26.57 +Goiania (BR) -49.27 -16.67 +Guarulhos (BR) -46.52 -23.47 +Praha (CZ) 14.47 50.07 +Varanasi (IN) 83 25.32 +Fez (MA) -4.97 34.05 +Milan (IT) 9.18 45.47 +Changwat Phetchabun (TH) 101.15 16.35 +Tripoli (LY) 13.17 32.88 +Port Harcourt (NG) 6.98 4.78 +Baile Atha Cliath (IE) -6.23 53.32 +Hiroshima (JP) 132.43 34.38 +Dubayy (AE) 55.27 25.25 +Managua (NI) -86.27 12.15 +Dubai (AE) 55.27 25.25 +Samara (RU) 50.13 53.2 +Omsk (RU) 73.4 55 +Benin (NG) 5.62 6.32 +Monterrey (MX) -100.32 25.67 +Baku (AZ) 49.87 40.38 +Brazzaville (CG) 15.28 -4.25 +Belgrade (RS) 20.47 44.82 +Leon (MX) -101.67 21.12 +Maiduguri (NG) 13.15 11.83 +Wuxi (CN) 120.28 31.57 +Kazan (RU) 49.12 55.75 +Al Jadida (MA) -8.5 33.25 +Yerevan (AM) 44.5 40.17 +Amritsar (IN) 74.85 31.62 +Kaisaria (TR) 35.48 38.72 +Copenhagen (DK) 12.57 55.67 +Ouagadouga (BF) -1.52 12.37 +Changwat Kalasin (TH) 103.5 16.37 +Taichung (TW) 120.67 24.13 +Yono (JP) 139.62 35.87 +Rostov-na-donu (RU) 39.7 47.23 +Adelaide (AU) 138.58 -34.92 +Allahabad (IN) 81.83 25.43 +Gaziantep (TR) 37.37 37.05 +Visakhapatnam (IN) 83.28 17.68 +Chelyabinsk (RU) 61.42 55.15 +Sofia (BG) 23.32 42.67 +Unguja Ukuu (TZ) 39.37 -6.32 +Datong (CN) 113.58 40.03 +Dagu (CN) 113.28 40.08 +Tbilisi (GE) 44.78 41.72 +Changwat Nonthaburi (TH) 100.47 13.82 +Changwat Samut Prakan (TH) 100.58 13.58 +Sendai (JP) 140.88 38.25 +Xianyang (CN) 108.7 34.33 +Ufa (RU) 56.03 54.77 +Songnam (KR) 127.13 37.43 +Campinas (BR) -47.07 -22.88 +Ouagadougou (BF) -1.52 12.37 +Jabalpur (IN) 79.95 23.17 +Haora (IN) 88.3 22.58 +Huainan (CN) 116.98 32.62 +Dublin (IE) -6.23 53.32 +Kunming (CN) 102.72 25.03 +Brussels (BE) 4.32 50.82 +Aurangabad (IN) 75.32 19.87 +Qom (IR) 50.87 34.63 +Volgograd (RU) 44.58 48.8 +Aidin (TR) 27.83 37.83 +Shenzhen (CN) 114.12 22.52 +Nova Iguacu (BR) -43.43 -22.75 +Rongcheng (CN) 116.35 23.52 +Odesa (UA) 30.72 46.47 +Kitakyushu (JP) 130.82 33.82 +Sholapur (IN) 75.92 17.67 +Baoding (CN) 115.48 38.85 +Changwat Surat Thani (TH) 99.32 9.12 +Napoli (IT) 14.25 40.82 +Benxi (CN) 123.75 41.28 +Zapopan (MX) -103.4 20.72 +Birmingham (GB) -1.92 52.47 +Perm (RU) 56.25 58 +Naples (IT) 14.25 40.82 +Erzerum (TR) 41.27 39.9 +Srinagar (IN) 74.8 34.08 +Zaria (NG) 7.7 11.07 +Guatemala (GT) -90.52 14.62 +Mendoza (AR) -68.82 -32.87 +Changwat Phitsanulok (TH) 100.25 16.82 +Cologne (DE) 6.95 50.92 +Calgary (CA) -114.07 51.07 +Port Elizabeth (ZA) 25.57 -33.97 +Warab (SD) 28.62 8.12 +Fes (MA) -4.97 34.05 +Koeln (DE) 6.95 50.92 +Coimbatore (IN) 76.95 10.98 +Maceio (BR) -35.72 -9.67 +Cartagena (CO) -75.5 10.38 +Settat (MA) -7.62 33 +Changzhou (CN) 119.97 31.77 +Sultanah (SA) 39.57 24.5 +Ranchi (IN) 85.32 23.35 +Marrakesh (MA) -8 31.62 +Changwat Nong Khai (TH) 102.73 17.87 +Sao Goncalo (BR) -43.02 -22.8 +Maha Sarakam (TH) 103.28 16.17 +Changwat Maha Sarakham (TH) 103.22 16.17 +Monrovia (LR) -10.8 6.3 +Irbil (IQ) 44 36.18 +Malatia (TR) 38.3 38.35 +Jodhpur (IN) 73.02 26.28 +Chiba (JP) 140.12 35.6 +Sao Luis (BR) -44.27 -2.52 +Chandigarh (IN) 76.78 30.73 +Gampheang Phet (TH) 99.5 16.47 +Madurai (IN) 78.12 9.92 +Ad Diwaniyah (IQ) 44.92 31.98 +Krasnoyarsk (RU) 92.78 56 +Huaibei (CN) 116.78 33.97 +Cochabamba (BO) -66.15 -17.37 +Ghom (IR) 50.87 34.63 +Abu Ghurayb (IQ) 43.98 33.28 +Abobo (CI) -4.02 5.42 +Guwahati (IN) 91.72 26.17 +Aba (NG) 7.37 5.12 +San Jose (US) -121.88 37.33 +Bulawayo (ZW) 28.57 -20.13 +Bishkek (KG) 74.6 42.87 +Pingdingshan (CN) 113.3 33.73 +Detroit (US) -83.03 42.32 +Changwat Rat Buri (TH) 99.78 13.52 +Gwalior (IN) 78.17 26.22 +Qiqihar (CN) 123.97 47.33 +Klang (MY) 101.45 3.02 +Safi (MA) -9.23 32.28 +Konya (TR) 32.47 37.85 +Mbuji-mayi (CD) 23.6 -6.15 +Vijayawada (IN) 80.62 16.52 +Ottawa (CA) -75.7 45.42 +Maisuru (IN) 76.63 12.3 +Changwat Suphan Buri (TH) 100.12 14.47 +Wenzhou (CN) 120.65 28.02 +Torino (IT) 7.67 45.03 +Saratov (RU) 46.02 51.57 +Ahvaz (IR) 48.68 31.32 +Tegucigalpa (HN) -87.22 14.08 +Turin (IT) 7.67 45.03 +Naucalpan (MX) -99.23 19.47 +Da Huryee (MN) 106.92 47.92 +Arequipa (PE) -71.53 -16.38 +Voronezh (RU) 39.17 51.65 +Padang (ID) 100.33 -0.95 +Hubli (IN) 75.17 15.33 +Marrakech (MA) -8 31.62 +Callao (PE) -77.15 -12.07 +Lvov (UA) 24 49.82 +Tucuman (AR) -65.22 -26.82 +Tangier (MA) -5.8 35.78 +Changwat Lampang (TH) 99.5 18.28 +Edmonton (CA) -113.5 53.53 +Duque De Caxias (BR) -43.3 -22.78 +Jos (NG) 8.9 9.92 +Sale (MA) -6.8 34.03 +Ilorin (NG) 4.53 8.5 +La Paz (BO) -68.15 -16.5 +Barquisimeto (VE) -69.32 10.07 +Oslo (NO) 10.75 59.92 +Nanning (CN) 108.32 22.82 +Johor Bahru (MY) 103.75 1.47 +Bandar Lampung (ID) 105.27 -5.45 +Cebu City (PH) 123.88 10.3 +Mombasa (KE) 39.67 -4.03 +Asgabat (TM) 58.37 37.95 +Jacksonville (US) -81.65 30.32 +Aleksandrovsk (UA) 35.17 47.82 +Lobh Buri (TH) 100.62 14.8 +Marseille (FR) 5.4 43.28 +Nagara Pathom (TH) 100.03 13.82 +Kathmandu (NP) 85.32 27.72 +Rupandehi (NP) 83.27 27.47 +Jalandhar (IN) 75.57 31.32 +Thiruvananthapuram (IN) 76.95 8.5 +Sakai (JP) 135.47 34.57 +Anyang (CN) 114.32 36.08 +San Miguel De Tucuman (AR) -65.22 -26.82 +Changwat Phra Nakhon Si Ayutthaya (TH) 100.53 14.33 +Selam (IN) 78.17 11.65 +Taroudannt (MA) -8.87 30.47 +Tiruchchirappalli (IN) 78.68 10.8 +Hims (SY) 36.72 34.72 +Hohhot (CN) 111.65 40.8 +Niamey (NE) 2.12 13.52 +Niamey (NE) 1.77 13.67 +Indianapolis (US) -86.15 39.77 +Valencia (ES) -0.37 39.47 +Changwat Nakhon Phanom (TH) 104.72 17.37 +Bogor (ID) 106.78 -6.58 +Lodz (PL) 19.47 51.75 +Ad Dammam (SA) 50.1 26.42 +Padumdhani (TH) 100.52 14.02 +Xining (CN) 101.77 36.62 +Kermanshah (IR) 47.05 34.3 +Kahriz (IR) 47.12 34.3 +Liuzhou (CN) 109.38 24.3 +Kota (IN) 75.82 25.17 +Natal (BR) -35.22 -5.77 +Bhubaneswar (IN) 85.82 20.22 +Qinhuangdao (CN) 119.58 39.92 +Hengyang (CN) 112.6 26.88 +Antalya (TR) 30.68 36.9 +Cebu (PH) 123.88 10.3 +Adalia (TR) 30.68 36.9 +Krakow (PL) 19.92 50.07 +Aligarh (IN) 78.07 27.87 +Da Nang (VN) 108.22 16.07 +Naradhivas (TH) 101.82 6.42 +Pietermaritzburg (ZA) 30.37 -29.62 +Taian (CN) 117.12 36.18 +Trujillo (PE) -79.02 -8.12 +Lome (TG) 1.22 6.12 +Lome (TG) 1 6.48 +Malang (ID) 112.62 -7.97 +Ciudad Guayana (VE) -62.65 8.35 +Amsterdam (NL) 4.92 52.35 +Kigali (RW) 30.05 -1.95 +Bareli (IN) 79.42 28.35 +Kigali (RW) 29.93 -2.28 +Kigale (RW) 30.05 -1.95 +Teresina (BR) -42.82 -5.07 +Poti (BR) -42.82 -5.02 +Xinxiang (CN) 113.87 35.3 +Sao Bernardo Do Campo (BR) -46.53 -23.68 +Hegang (CN) 130.37 47.38 +Riga (LV) 24.1 56.95 +Taza (MA) -4.02 34.22 +Astrida (RW) 29.73 -2.58 +Columbus (US) -82.98 39.95 +Oyo (NG) 3.92 7.83 +Tainan (TW) 120.2 22.98 +Quetta (PK) 67 30.18 +San Francisco (US) -122.42 37.77 +Jhapa (NP) 87.83 26.47 +Campo Grande (BR) -54.62 -20.43 +Athens (GR) 23.72 37.97 +Ashgabat (TM) 58.37 37.95 +Sar-e Pol (AF) 66.7 35.53 +Guadalupe (MX) -100.25 25.67 +As Sulaymaniyah (IQ) 45.43 35.55 +Dhanukha (NP) 86.07 26.82 +Cucuta (CO) -72.5 7.87 +Moradabad (IN) 78.77 28.82 +Langfang (CN) 116.68 39.5 +Ningbo (CN) 121.53 29.87 +Yantai (CN) 121.4 37.52 +Tolyatti (RU) 49.4 53.52 +Merida (MX) -89.62 20.97 +Tlalnepantla (MX) -99.22 19.52 +Jerusalem (IL) 35.22 31.77 +Chisinau (MD) 28.85 47 +Kailali (NP) 80.77 28.57 +Chonju (KR) 127.13 35.82 +Nouakchott (MR) -15.97 18.08 +Zhuzhou (CN) 113.12 27.68 +Chihuahua (MX) -106.07 28.62 +Bhiwandi (IN) 73.07 19.3 +Jaboatao (BR) -35.02 -8.12 +Rajshahi (BD) 88.58 24.37 +Zagreb (HR) 16 45.78 +Agadair (MA) -9.58 30.38 +Bosna-sarai (BA) 18.37 43.85 +Eva Peron (AR) -57.93 -34.92 +Tunes (TN) 10.17 36.8 +Zhangjiakou (CN) 114.87 40.8 +Cluj (RO) 23.6 46.77 +Cotonou (BJ) 2.42 6.33 +Zigong (CN) 104.77 29.38 +Fuxin (CN) 121.65 42 +Enuga (NG) 7.47 6.42 +Tanger (MA) -5.8 35.78 +Liaoyang (CN) 123.05 41.22 +Sevilla (ES) -5.98 37.37 +Nerima (JP) 139.65 35.72 +La Plata (AR) -57.93 -34.92 +Bangui (CF) 18.57 4.37 +Kumamoto (JP) 130.72 32.78 +Raipur (IN) 81.62 21.22 +Austin (US) -97.73 30.27 +Adiyaman (TR) 38.27 37.75 +Osasco (BR) -46.77 -23.57 +San Luis Potosi (MX) -100.97 22.13 +Leui (TH) 101.72 17.47 +Changwat Loei (TH) 101.72 17.42 +Gorakhpur (IN) 83.37 26.75 +Ipoh (MY) 101.07 4.57 +Zibo (CN) 118.05 36.78 +Palermo (IT) 13.37 38.12 +Changwat Chachoengsao (TH) 101.07 13.68 +Mississauga (CA) -79.5 43.13 +Taounate (MA) -4.65 34.53 +Puyang (CN) 115 35.7 +Nantong (CN) 120.87 32.02 +Changwat Sukhothai (TH) 99.82 17 +Changwat Sawankhalok (TH) 99.83 17.3 +Mudanjiang (CN) 129.58 44.57 +Santo Andre (BR) -46.52 -23.67 +Pointe-noire (CG) 11.83 -4.78 +Aguascalientes (MX) -102.28 21.87 +Agadir (MA) -9.58 30.38 +Hamilton (CA) -79.82 43.25 +Enugu (NG) 7.47 6.42 +Kryvyy Rih (UA) 33.35 47.92 +Acapulco (MX) -99.92 16.85 +Joao Pessoa (BR) -34.87 -7.12 +Ansan (KR) 126.82 37.32 +Benghazi (LY) 20.07 32.12 +Memphis (US) -90.13 34.92 +Frankfurt Am Main (DE) 8.67 50.12 +Krasnodar (RU) 38.97 45.02 +Shaoyang (CN) 111.25 27 +Guilin (CN) 110.27 25.27 +Sagamihara (JP) 139.35 35.55 +Zamboanga City (PH) 122.07 6.9 +Colombo (LK) 79.83 6.92 +Frankfurt (DE) 8.67 50.12 +Lilongwe (MW) 33.77 -13.97 +Wahran (DZ) -0.63 35.68 +Mar Del Plata (AR) -57.53 -38 +Quebec (CA) -71.25 46.78 +Diyarbakir (TR) 40.2 37.92 +New South Memphis (US) -90.05 35.08 +Memphis (US) -90.03 35.13 +Ulyanovsk (RU) 48.38 54.32 +Okayama (JP) 133.92 34.63 +Zhanjiang (CN) 110.37 21.18 +Yogyakarta (ID) 110.37 -7.78 +Zaragoza (ES) -0.87 41.62 +Wroclaw (PL) 17.02 51.1 +Anyang (KR) 126.92 37.38 +Zhenjiang (CN) 119.43 32.2 +Winnipeg (CA) -97.17 49.87 +Dandong (CN) 124.38 40.12 +Izhevsk (RU) 53.22 56.85 +Shaoguan (CN) 113.57 24.8 +Yancheng (CN) 120.12 33.38 +Foshan (CN) 113.12 23.02 +Contagem (BR) -44.1 -19.92 +Bhilai (IN) 81.42 21.22 +Panshan (CN) 122.03 41.18 +Jibuti (DJ) 43.13 11.58 +Saltillo (MX) -101 25.42 +Ash Shariqah (AE) 55.38 25.35 +Fort Worth (US) -97.32 32.72 +El-hodeidah (YE) 42.95 14.78 +Jamshedpur (IN) 86.17 22.8 +Tandjile (TD) 16.1 9.32 +Haikou (CN) 110.33 20.03 +Changwat Phichit (TH) 100.37 16.42 +Sao Jose Dos Campos (BR) -45.87 -23.17 +Changwat Trang (TH) 99.58 7.53 +Mersin (TR) 34.63 36.72 +Taizhou (CN) 119.9 32.48 +Queretaro (MX) -100.37 20.6 +Xingtai (CN) 114.48 37.05 +Baltimore (US) -76.6 39.28 +Glasgow (GB) -4.25 55.82 +Yaroslavl (RU) 39.87 57.62 +Elazig (TR) 39.22 38.67 +Benoni (ZA) 28.32 -26.17 +Hamamatsu (JP) 137.72 34.7 +Kochi (IN) 76.22 9.97 +Amravati (IN) 77.75 20.92 +Rotterdam (NL) 4.5 51.92 +Amaravati (IN) 77.75 20.92 +Abu Dhabi (AE) 54.37 24.47 +Hai Phong (VN) 106.67 20.85 +Orumiyeh (IR) 45.1 37.55 +Genova (IT) 8.93 44.42 +Islamabad (PK) 73.17 33.7 +Kirkuk (IQ) 44.38 35.47 +Barnaul (RU) 83.75 53.35 +Charlotte (US) -80.83 35.22 +El Paso (US) -106.48 31.75 +Luancheng (CN) 114.65 37.87 +Mexicali (MX) -115.47 32.65 +Hermosillo (MX) -110.97 29.07 +Rasht (IR) 49.58 37.27 +Dortmund (DE) 7.45 51.52 +Kayseri (TR) 35.48 38.72 +Abeokuta (NG) 3.35 7.15 +Caesarea (TR) 35.48 38.72 +Morelia (MX) -101.12 19.68 +Stuttgart (DE) 9.17 48.77 +Yingkou (CN) 122.5 40.63 +Eiko (CN) 122.22 40.67 +Chimalhuacan (MX) -98.9 19.42 +Zhangzhou (CN) 117.65 24.5 +Vladivostok (RU) 131.9 43.12 +Irkutsk (RU) 104.32 52.27 +Belfast (GB) -5.92 54.57 +Genoa (IT) 8.93 44.42 +Blantyre (MW) 35 -15.77 +Kingston (JM) -76.78 18 +Chiclayo (PE) -79.83 -6.77 +Culiacan (MX) -107.38 24.78 +Cuttack (IN) 85.82 20.5 +Hachioji (JP) 139.32 35.65 +Milwaukee (US) -87.9 43.03 +Xiamen (CN) 118.08 24.47 +Khabarovsk (RU) 135.08 48.5 +Ussuriyskiy (RU) 135.05 48.43 +Khabarovsk Vtoroy (RU) 135.13 48.43 +Libreville (GA) 9.43 0.37 +Kerman (IR) 57.08 30.28 +Dusseldorf (DE) 6.77 51.22 +Kaifeng (CN) 114.42 34.73 +Essen (DE) 7.02 51.45 +Bengbu (CN) 117.35 32.93 +Bikaner (IN) 73.28 28.02 +Banjarmasin (ID) 114.57 -3.32 +Shihezi (CN) 86.02 44.28 +Bouake (CI) -5.02 7.67 +Bucaramanga (CO) -73.12 7.12 +South Boston (US) -71.03 42.32 +Kuching (MY) 110.32 1.55 +Poznan (PL) 16.97 52.42 +Seattle (US) -122.32 47.6 +Veracruz (MX) -96.12 19.18 +Lisboa (PT) -9.12 38.72 +Asmara (ER) 38.92 15.32 +Sokoto (NG) 5.22 13.05 +Uberlandia (BR) -48.28 -18.92 +Onitsha (NG) 6.77 6.17 +Onicha (NG) 6.4 5.82 +Funabashi (JP) 139.97 35.68 +Hamhung (KP) 127.53 39.9 +Sorocaba (BR) -47.45 -23.47 +Helsinki (FI) 24.93 60.17 +Malaga (ES) -4.42 36.72 +Warangal (IN) 79.57 18 +Denver (US) -104.98 39.73 +Santiago (DO) -70.7 19.43 +Santiago De Cuba (CU) -75.82 20.02 +Surakarta (ID) 110.82 -7.57 +Kagoshima (JP) 130.55 31.6 +Huaiyin (CN) 119.02 33.58 +Bhavnagar (IN) 72.15 21.77 +Mar De Plata (AR) -57.53 -38 +Bahawalpur (PK) 71.67 29.38 +Washington (US) -77.03 38.88 +Zahedan (IR) 60.87 29.48 +Changwat Prachuap Khiri Khan (TH) 99.72 11.75 +Ribeirao Preto (BR) -47.78 -21.17 +Hamitabat (TR) 30.55 37.75 +Aden (YE) 45.03 12.77 +Chkalov (RU) 55.37 51.75 +Orenburgskiy (RU) 54.92 51.88 +Orenburg (RU) 55.08 51.78 +Jiamusi (CN) 130.33 46.82 +Antipolo (PH) 121.37 14.12 +Antipolo (PH) 121.17 14.58 +Salta (AR) -65.42 -24.77 +Chandaburi (TH) 102.15 12.58 +Neijiang (CN) 105.05 29.58 +Bremen (DE) 8.8 53.07 +Meknes (MA) -5.53 33.88 +Sharjah (AE) 55.38 25.35 +Matola (MZ) 32.45 -25.95 +Changwat Yasothon (TH) 104.13 15.78 +Al Sharjah (AE) 55.38 25.35 +Djuschambe (TJ) 68.77 38.55 +Sargodha (PK) 72.67 32.08 +Vilnius (LT) 25.32 54.67 +Cancun (MX) -86.82 21.17 +Portland (US) -122.67 45.52 +Maanshan (CN) 118.47 31.72 +Las Vegas (US) -115.13 36.17 +Changwat Tak (TH) 99.12 16.87 +Yangzhou (CN) 119.43 32.38 +Novokuznetsk (RU) 87.08 53.75 +Kisangani (CD) 25.18 0.52 +Warri (NG) 5.75 5.52 +Yongkang (CN) 120.02 28.87 +Tanggu (CN) 117.63 39.02 +Oklahoma City (US) -97.5 35.47 +Jiangmen (CN) 113.07 22.57 +Changwat Nan (TH) 100.7 18.68 +Nashville (US) -86.78 36.15 +Beira (MZ) 34.83 -19.83 +Guntur (IN) 80.45 16.3 +Yueyang (CN) 113.1 29.13 +Cangzhou (CN) 116.87 38.32 +Vaucluse (FR) 5.12 43.92 +San Salvador (SV) -89.2 13.7 +Changwat Yala (TH) 101.25 6.5 +Torreon (MX) -103.42 25.55 +Dehra Dun (IN) 78.02 30.32 +Cuiaba (BR) -56.07 -15.57 +Khemisset (MA) -6.07 33.82 +Lopez Mateos (MX) -99.25 19.55 +Petaling Jaya (MY) 101.65 3.07 +Ryazan (RU) 39.73 54.62 +Hanover (DE) 9.72 52.37 +Tyumen (RU) 65.52 57.15 +Durgapur (IN) 87.32 23.47 +Tucson (US) -110.92 32.22 +Quilmes (AR) -58.27 -34.72 +Ajmer (IN) 74.62 26.43 +Felicitas Julia (PT) -9.12 38.72 +Changde (CN) 111.67 29.02 +Jiaozuo (CN) 113.22 35.23 +Ulhasnagar (IN) 73.15 19.22 +Kolhapur (IN) 74.22 16.68 +Lipetsk (RU) 39.57 52.62 +Shiliguri (IN) 88.42 26.7 +Goteborg (SE) 11.97 57.72 +Eskisehir (TR) 30.52 39.77 +Hamadan (IR) 48.5 34.78 +Azadshahr (IR) 48.53 34.78 +Penza (RU) 45 53.18 +Changwat Phatthalung (TH) 99.97 7.57 +Changwat Chumphon (TH) 99.17 10.5 +Tembisa (ZA) 28.22 -25.98 +Changwat Uttaradit (TH) 100.1 17.62 +Nikolaev (UA) 32 46.97 +Khenifra (MA) -5.67 32.93 +Naberezhnyye Morkvashi (RU) 48.83 55.75 +Naberezhnyye Chelny (RU) 52.42 55.75 +Asuncion (PY) -57.67 -25.27 +San Nicolas De Los Garza (MX) -100.28 25.75 +Wuhu (CN) 118.53 31.17 +Dang (NP) 82.28 28.12 +Toluca (MX) -99.67 19.28 +Niigata (JP) 139.05 37.92 +Duisburg (DE) 6.75 51.42 +Asansol (IN) 86.97 23.67 +Azilal (MA) -6.57 31.97 +Asanol (IN) 86.97 23.67 +Arak (IR) 49.68 34.08 +Astrakhan (RU) 48.03 46.33 +Cagayan De Oro City (PH) 124.63 8.47 +Zhuhai (CN) 113.57 22.27 +Gold Coast (AU) 153.42 -28 +Wahren (DE) 12.32 51.37 +Bejraburi (TH) 99.95 13.08 +Oshogbo (NG) 4.57 7.77 +Las Pinas (PH) 120.97 14.45 +Shashi (CN) 112.23 30.3 +Reynosa (MX) -98.27 26.07 +Makhachkala (RU) 47.5 42.97 +Newcastle (AU) 151.75 -32.92 +Nuremberg (DE) 11.07 49.43 +Khouribga (MA) -6.9 32.87 +Ouarzazate (MA) -6.92 30.92 +Chimahi (ID) 107.53 -6.87 +Tlaquepaque (MX) -103.32 20.63 +Taguig (PH) 121.07 14.52 +Leipzig (DE) 12.32 51.28 +Jamnagar (IN) 70.07 22.47 +Panchiao (TW) 121.52 25.03 +Cibitoke (BI) 29.37 -3.33 +Aracaju (BR) -37.07 -10.92 +San Pedro Sula (HN) -88.02 15.5 +As Suways (EG) 32.53 29.97 +Albuquerque (US) -106.65 35.08 +Tomsk (RU) 84.97 56.5 +Matsuyama (JP) 132.75 33.83 +Nanded (IN) 77.32 19.13 +Saharanpur (IN) 77.53 29.97 +Gulbarga (IN) 76.82 17.32 +Bhatpara (IN) 88.4 22.87 +Long Beach (US) -118.18 33.77 +An Najaf (IQ) 44.3 31.98 +Feira De Santana (BR) -38.95 -12.25 +Shah Alam (MY) 101.52 3.07 +Himeji (JP) 134.68 34.82 +Tuxtla Gutierrez (MX) -93.12 16.75 +Gomel (BY) 30.97 52.43 +Dresden (DE) 13.75 51.03 +Okene (NG) 6.22 7.53 +Uijongbu (KR) 127.03 37.73 +Hargeysa (SO) 44.07 9.57 +Yazd (IR) 54.37 31.88 +Hargeisa (SO) 44.07 9.57 +Sialkot (PK) 74.52 32.5 +Kemerovo (RU) 86.07 55.32 +Yichang (CN) 111.28 30.7 +The Hague (NL) 4.28 52.07 +Tipaza (DZ) 2.43 36.58 +Cuautitlan Izcalli (MX) -99.23 19.63 +Yinchuan (CN) 106.27 38.47 +Skopje (MK) 21.42 42 +Vereeniging (ZA) 27.92 -26.67 +Maoming (CN) 110.9 21.63 +Londrina (BR) -51.13 -23.3 +Larache (MA) -6.15 35.18 +Jiaojiang (CN) 121.43 28.67 +Matsudo (JP) 139.9 35.77 +Juiz De Fora (BR) -43.35 -21.75 +San Juan (AR) -68.53 -31.53 +Liverpool (GB) -3 53.42 +Nishinomiya (JP) 135.32 34.72 +Tula (RU) 37.6 54.2 +Kawaguchi (JP) 139.72 35.8 +Sacramento (US) -121.48 38.57 +Shizuoka (JP) 138.37 34.97 +Zunyi (CN) 106.82 27.53 +Jiaxing (CN) 120.73 30.75 +Belford Roxo (BR) -43.38 -22.75 +Jammu (IN) 74.87 32.72 +Dongliao (CN) 125.13 42.9 +Fresno (US) -119.77 36.73 +Lyon (FR) 4.83 45.75 +Kananga (CD) 22.42 -5.88 +Bloemfontein (ZA) 26.18 -29.12 +Xiangfan (CN) 112.13 32.03 +Gdansk (PL) 18.67 54.35 +Calabar (NG) 8.32 4.95 +Panzhihua (CN) 101.72 26.55 +Joinville (BR) -48.82 -26.3 +Zamboanga (PH) 122.07 6.9 +Hamah (SY) 36.75 35.12 +Mixco (GT) -90.6 14.62 +Antwerp (BE) 4.42 51.22 +General Santos City (PH) 125.17 6.1 +Boayan (PH) 125.23 6.1 +New Orleans (US) -90.07 29.95 +Kanazawa (JP) 136.65 36.57 +Ichikawa (JP) 139.92 35.72 +Burleigh School (PH) 122.07 6.9 +Ujjain (IN) 75.77 23.17 +Kirov (RU) 49.65 58.58 +Kota Kinabalu (MY) 116.07 5.97 +Durango (MX) -104.67 24.02 +Niteroi (BR) -43.08 -22.88 +Hengshui (CN) 115.7 37.72 +Chitungwiza (ZW) 31.03 -17.98 +Santa Fe (AR) -60.7 -31.62 +Gabouk (SA) 36.57 28.37 +Pontianak (ID) 109.32 -0.02 +Leeds (GB) -1.57 53.78 +Bacolod City (PH) 122.95 10.65 +Sao Joao De Meriti (BR) -43.35 -22.8 +Mansilingan (PH) 122.97 10.62 +Bacolod (PH) 122.95 10.65 +Essaouira (MA) -9.77 31.5 +Manado (ID) 124.83 1.48 +Jining (CN) 116.57 35.4 +Constantine (DZ) 6.6 36.35 +Mesa (US) -111.82 33.42 +Utsunomiya (JP) 139.87 36.53 +Urfa (TR) 38.78 37.15 +Cleveland (US) -81.68 41.48 +Virginia Beach (US) -75.97 36.85 +Chengde (CN) 118.17 40.77 +Xuchang (CN) 113.82 34.02 +Oita (JP) 131.6 33.23 +Sheffield (GB) -1.5 53.37 +Cheboksary (RU) 47.25 56.12 +Cagayan De Oro (PH) 124.63 8.47 +Boksburg (ZA) 28.25 -26.22 +Kalat (AF) 66.9 32.1 +Rajpur (IN) 88.42 22.4 +Changwat Krabi (TH) 98.92 8.07 +Amagasaki (JP) 135.42 34.72 +Malatya (TR) 38.3 38.35 +North Kansas City (US) -94.55 39.12 +Kansas City (US) -94.57 39.08 +Kansas City (US) -94.62 39.1 +Dasmarinas (PH) 120.93 14.32 +Dasmarinas (PH) 121.02 14.53 +Nangi (IN) 88.2 22.5 +Pereira (CO) -75.68 4.8 +Calicut (IN) 75.77 11.25 +Carrefour (HT) -72.4 18.53 +Iquitos (PE) -73.23 -3.73 +Mawlamyine (MM) 97.62 16.48 +Baoji (CN) 107.37 34.35 +Kurashiki (JP) 133.77 34.57 +Garoua (CM) 13.4 9.3 +Mwanza (TZ) 32.88 -2.52 +Kousseri (CM) 15.02 12.07 +Tirunelveli (IN) 77.7 8.72 +Edinburgh (GB) -3.2 55.95 +Fort Fureau (CM) 15.02 12.07 +Malegaon (IN) 74.52 20.55 +Matamoros (MX) -97.5 25.87 +Kaliningrad (RU) 20.5 54.7 +Geneve (CH) 6.17 46.2 +Ananindeua (BR) -48.37 -1.37 +Balikpapan (ID) 116.82 -1.27 +Brampton (CA) -79.77 43.67 +Dadiangas (PH) 125.17 6.1 +Namangan (UZ) 71.67 40.98 +Katsina (NG) 7.58 12.98 +Welkom (ZA) 26.72 -27.97 +Santa Marta (CO) -74.2 11.23 +El Mahalla El Kubra (EG) 31.17 30.97 +Bristol (GB) -2.57 51.45 +Yokosuka (JP) 139.67 35.28 +Akola (IN) 77 20.72 +Belgaum (IN) 74.5 15.87 +# These have been added back by hand +Cairo (EG) 31.25 30.06 +Zurich (CH) 8.55 47.36 diff --git a/perftestflows/src/test/kotlin/net/corda/ptflows/contract/asset/PtCashTests.kt b/perftestflows/src/test/kotlin/net/corda/ptflows/contract/asset/PtCashTests.kt new file mode 100644 index 0000000000..2f75481982 --- /dev/null +++ b/perftestflows/src/test/kotlin/net/corda/ptflows/contract/asset/PtCashTests.kt @@ -0,0 +1,896 @@ +package net.corda.ptflows.contract.asset + + +import net.corda.core.contracts.* +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.generateKeyPair +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.AnonymousParty +import net.corda.core.identity.Party +import net.corda.core.node.ServiceHub +import net.corda.core.node.services.Vault +import net.corda.core.node.services.VaultService +import net.corda.core.node.services.queryBy +import net.corda.core.transactions.SignedTransaction +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.transactions.WireTransaction +import net.corda.core.utilities.OpaqueBytes +import net.corda.ptflows.* +import net.corda.ptflows.utils.sumCash +import net.corda.ptflows.utils.sumCashBy +import net.corda.ptflows.utils.sumCashOrNull +import net.corda.ptflows.utils.sumCashOrZero +import net.corda.node.services.vault.NodeVaultService +import net.corda.node.utilities.CordaPersistence +import net.corda.ptflows.contracts.asset.* +import net.corda.testing.* +import net.corda.testing.contracts.DummyState +import net.corda.testing.contracts.calculateRandomlySizedAmounts +import net.corda.testing.node.MockServices +import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices +import org.junit.After +import org.junit.Before +import org.junit.Test +import java.security.KeyPair +import java.util.* +import kotlin.test.* + +/** + * Creates a random set of between (by default) 3 and 10 cash states that add up to the given amount and adds them + * to the vault. This is intended for unit tests. The cash is issued by [DUMMY_CASH_ISSUER] and owned by the legal + * identity key from the storage service. + * + * The service hub needs to provide at least a key management service and a storage service. + * + * @param issuerServices service hub of the issuer node, which will be used to sign the transaction. + * @param outputNotary the notary to use for output states. The transaction is NOT signed by this notary. + * @return a vault object that represents the generated states (it will NOT be the full vault from the service hub!). + */ +fun ServiceHub.fillWithSomeTestCash(howMuch: Amount, + issuerServices: ServiceHub = this, + outputNotary: Party = DUMMY_NOTARY, + atLeastThisManyStates: Int = 3, + atMostThisManyStates: Int = 10, + rng: Random = Random(), + ref: OpaqueBytes = OpaqueBytes(ByteArray(1, { 1 })), + ownedBy: AbstractParty? = null, + issuedBy: PartyAndReference = DUMMY_CASH_ISSUER): Vault { + val amounts = calculateRandomlySizedAmounts(howMuch, atLeastThisManyStates, atMostThisManyStates, rng) + + val myKey = ownedBy?.owningKey ?: myInfo.chooseIdentity().owningKey + val anonParty = AnonymousParty(myKey) + + // We will allocate one state to one transaction, for simplicities sake. + val cash = PtCash() + val transactions: List = amounts.map { pennies -> + val issuance = TransactionBuilder(null as Party?) + cash.generateIssue(issuance, Amount(pennies, Issued(issuedBy.copy(reference = ref), howMuch.token)), anonParty, outputNotary) + + return@map issuerServices.signInitialTransaction(issuance, issuedBy.party.owningKey) + } + + recordTransactions(transactions) + + // Get all the StateRefs of all the generated transactions. + val states = transactions.flatMap { stx -> + stx.tx.outputs.indices.map { i -> stx.tx.outRef(i) } + } + + return Vault(states) +} + + +class PtCashTests : TestDependencyInjectionBase() { + val defaultRef = OpaqueBytes(ByteArray(1, { 1 })) + val defaultIssuer = MEGA_CORP.ref(defaultRef) + val inState = PtCash.State( + amount = 1000.DOLLARS `issued by` defaultIssuer, + owner = AnonymousParty(ALICE_PUBKEY) + ) + // Input state held by the issuer + val issuerInState = inState.copy(owner = defaultIssuer.party) + val outState = issuerInState.copy(owner = AnonymousParty(BOB_PUBKEY)) + + fun PtCash.State.editDepositRef(ref: Byte) = copy( + amount = Amount(amount.quantity, token = amount.token.copy(amount.token.issuer.copy(reference = OpaqueBytes.of(ref)))) + ) + + lateinit var miniCorpServices: MockServices + lateinit var megaCorpServices: MockServices + val vault: VaultService get() = miniCorpServices.vaultService + lateinit var database: CordaPersistence + lateinit var vaultStatesUnconsumed: List> + + @Before + fun setUp() { + LogHelper.setLevel(NodeVaultService::class) + megaCorpServices = MockServices(listOf("net.corda.ptflows.contracts.asset"), MEGA_CORP_KEY) + val databaseAndServices = makeTestDatabaseAndMockServices(cordappPackages = listOf("net.corda.ptflows.contracts.asset"), keys = listOf(MINI_CORP_KEY, MEGA_CORP_KEY, OUR_KEY)) + database = databaseAndServices.first + miniCorpServices = databaseAndServices.second + + // Create some cash. Any attempt to spend >$500 will require multiple issuers to be involved. + database.transaction { + miniCorpServices.fillWithSomeTestCash(howMuch = 100.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1, + ownedBy = OUR_IDENTITY_1, issuedBy = MEGA_CORP.ref(1), issuerServices = megaCorpServices) + miniCorpServices.fillWithSomeTestCash(howMuch = 400.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1, + ownedBy = OUR_IDENTITY_1, issuedBy = MEGA_CORP.ref(1), issuerServices = megaCorpServices) + miniCorpServices.fillWithSomeTestCash(howMuch = 80.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1, + ownedBy = OUR_IDENTITY_1, issuedBy = MINI_CORP.ref(1), issuerServices = miniCorpServices) + miniCorpServices.fillWithSomeTestCash(howMuch = 80.SWISS_FRANCS, atLeastThisManyStates = 1, atMostThisManyStates = 1, + ownedBy = OUR_IDENTITY_1, issuedBy = MINI_CORP.ref(1), issuerServices = miniCorpServices) + } + database.transaction { + vaultStatesUnconsumed = miniCorpServices.vaultQueryService.queryBy().states + } + resetTestSerialization() + } + + @After + fun tearDown() { + database.close() + } + + @Test + fun trivial() { + transaction { + attachment(PtCash.PROGRAM_ID) + input(PtCash.PROGRAM_ID) { inState } + + tweak { + output(PtCash.PROGRAM_ID) { outState.copy(amount = 2000.DOLLARS `issued by` defaultIssuer) } + command(ALICE_PUBKEY) { PtCash.Commands.Move() } + this `fails with` "the amounts balance" + } + tweak { + output(PtCash.PROGRAM_ID) { outState } + command(ALICE_PUBKEY) { DummyCommandData } + // Invalid command + this `fails with` "required net.corda.ptflows.contracts.asset.PtCash.Commands.Move command" + } + tweak { + output(PtCash.PROGRAM_ID) { outState } + command(BOB_PUBKEY) { PtCash.Commands.Move() } + this `fails with` "the owning keys are a subset of the signing keys" + } + tweak { + output(PtCash.PROGRAM_ID) { outState } + output(PtCash.PROGRAM_ID) { outState `issued by` MINI_CORP } + command(ALICE_PUBKEY) { PtCash.Commands.Move() } + this `fails with` "at least one cash input" + } + // Simple reallocation works. + tweak { + output(PtCash.PROGRAM_ID) { outState } + command(ALICE_PUBKEY) { PtCash.Commands.Move() } + this.verifies() + } + } + } + + @Test + fun `issue by move`() { + // Check we can't "move" money into existence. + transaction { + attachment(PtCash.PROGRAM_ID) + input(PtCash.PROGRAM_ID) { DummyState() } + output(PtCash.PROGRAM_ID) { outState } + command(MINI_CORP_PUBKEY) { PtCash.Commands.Move() } + + this `fails with` "there is at least one cash input for this group" + } + } + + @Test + fun issue() { + // 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(PtCash.PROGRAM_ID) + output(PtCash.PROGRAM_ID) { outState } + command(ALICE_PUBKEY) { PtCash.Commands.Issue() } + this `fails with` "output states are issued by a command signer" + } + transaction { + attachment(PtCash.PROGRAM_ID) + output(PtCash.PROGRAM_ID) { + PtCash.State( + amount = 1000.DOLLARS `issued by` MINI_CORP.ref(12, 34), + owner = AnonymousParty(ALICE_PUBKEY) + ) + } + command(MINI_CORP_PUBKEY) { PtCash.Commands.Issue() } + this.verifies() + } + } + + @Test + fun generateIssueRaw() { + initialiseTestSerialization() + // Test generation works. + val tx: WireTransaction = TransactionBuilder(notary = null).apply { + PtCash().generateIssue(this, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = AnonymousParty(ALICE_PUBKEY), notary = DUMMY_NOTARY) + }.toWireTransaction(miniCorpServices) + assertTrue(tx.inputs.isEmpty()) + val s = tx.outputsOfType().single() + assertEquals(100.DOLLARS `issued by` MINI_CORP.ref(12, 34), s.amount) + assertEquals(MINI_CORP as AbstractParty, s.amount.token.issuer.party) + assertEquals(AnonymousParty(ALICE_PUBKEY), s.owner) + assertTrue(tx.commands[0].value is PtCash.Commands.Issue) + assertEquals(MINI_CORP_PUBKEY, tx.commands[0].signers[0]) + } + + @Test + fun generateIssueFromAmount() { + initialiseTestSerialization() + // Test issuance from an issued amount + val amount = 100.DOLLARS `issued by` MINI_CORP.ref(12, 34) + val tx: WireTransaction = TransactionBuilder(notary = null).apply { + PtCash().generateIssue(this, amount, owner = AnonymousParty(ALICE_PUBKEY), notary = DUMMY_NOTARY) + }.toWireTransaction(miniCorpServices) + assertTrue(tx.inputs.isEmpty()) + assertEquals(tx.outputs[0], tx.outputs[0]) + } + + @Test + 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(PtCash.PROGRAM_ID) + input(PtCash.PROGRAM_ID) { issuerInState } + output(PtCash.PROGRAM_ID) { inState.copy(amount = inState.amount * 2) } + + // Move fails: not allowed to summon money. + tweak { + command(ALICE_PUBKEY) { PtCash.Commands.Move() } + this `fails with` "the amounts balance" + } + + // Issue works. + tweak { + command(MEGA_CORP_PUBKEY) { PtCash.Commands.Issue() } + this.verifies() + } + } + + // Can't use an issue command to lower the amount. + transaction { + attachment(PtCash.PROGRAM_ID) + input(PtCash.PROGRAM_ID) { inState } + output(PtCash.PROGRAM_ID) { inState.copy(amount = inState.amount.splitEvenly(2).first()) } + command(MEGA_CORP_PUBKEY) { PtCash.Commands.Issue() } + this `fails with` "output values sum to more than the inputs" + } + + // Can't have an issue command that doesn't actually issue money. + transaction { + attachment(PtCash.PROGRAM_ID) + input(PtCash.PROGRAM_ID) { inState } + output(PtCash.PROGRAM_ID) { inState } + command(MEGA_CORP_PUBKEY) { PtCash.Commands.Issue() } + this `fails with` "output values sum to more than the inputs" + } + + // Can't have any other commands if we have an issue command (because the issue command overrules them) + transaction { + attachment(PtCash.PROGRAM_ID) + input(PtCash.PROGRAM_ID) { inState } + output(PtCash.PROGRAM_ID) { inState.copy(amount = inState.amount * 2) } + command(MEGA_CORP_PUBKEY) { PtCash.Commands.Issue() } + tweak { + command(MEGA_CORP_PUBKEY) { PtCash.Commands.Issue() } + this `fails with` "there is only a single issue command" + } + this.verifies() + } + } + + /** + * Test that the issuance builder rejects building into a transaction with existing + * cash inputs. + */ + @Test(expected = IllegalStateException::class) + fun `reject issuance with inputs`() { + initialiseTestSerialization() + // Issue some cash + var ptx = TransactionBuilder(DUMMY_NOTARY) + + PtCash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP, notary = DUMMY_NOTARY) + val tx = miniCorpServices.signInitialTransaction(ptx) + + // Include the previously issued cash in a new issuance command + ptx = TransactionBuilder(DUMMY_NOTARY) + ptx.addInputState(tx.tx.outRef(0)) + PtCash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP, notary = DUMMY_NOTARY) + } + + @Test + fun testMergeSplit() { + // Splitting value works. + transaction { + attachment(PtCash.PROGRAM_ID) + command(ALICE_PUBKEY) { PtCash.Commands.Move() } + tweak { + input(PtCash.PROGRAM_ID) { inState } + val splits4 = inState.amount.splitEvenly(4) + for (i in 0..3) output(PtCash.PROGRAM_ID) { inState.copy(amount = splits4[i]) } + this.verifies() + } + // Merging 4 inputs into 2 outputs works. + tweak { + val splits2 = inState.amount.splitEvenly(2) + val splits4 = inState.amount.splitEvenly(4) + for (i in 0..3) input(PtCash.PROGRAM_ID) { inState.copy(amount = splits4[i]) } + for (i in 0..1) output(PtCash.PROGRAM_ID) { inState.copy(amount = splits2[i]) } + this.verifies() + } + // Merging 2 inputs into 1 works. + tweak { + val splits2 = inState.amount.splitEvenly(2) + for (i in 0..1) input(PtCash.PROGRAM_ID) { inState.copy(amount = splits2[i]) } + output(PtCash.PROGRAM_ID) { inState } + this.verifies() + } + } + } + + @Test + fun zeroSizedValues() { + transaction { + attachment(PtCash.PROGRAM_ID) + input(PtCash.PROGRAM_ID) { inState } + input(PtCash.PROGRAM_ID) { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) } + command(ALICE_PUBKEY) { PtCash.Commands.Move() } + this `fails with` "zero sized inputs" + } + transaction { + attachment(PtCash.PROGRAM_ID) + input(PtCash.PROGRAM_ID) { inState } + output(PtCash.PROGRAM_ID) { inState } + output(PtCash.PROGRAM_ID) { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) } + command(ALICE_PUBKEY) { PtCash.Commands.Move() } + this `fails with` "zero sized outputs" + } + } + + @Test + fun trivialMismatches() { + // Can't change issuer. + transaction { + attachment(PtCash.PROGRAM_ID) + input(PtCash.PROGRAM_ID) { inState } + output(PtCash.PROGRAM_ID) { outState `issued by` MINI_CORP } + command(ALICE_PUBKEY) { PtCash.Commands.Move() } + this `fails with` "the amounts balance" + } + // Can't change deposit reference when splitting. + transaction { + attachment(PtCash.PROGRAM_ID) + val splits2 = inState.amount.splitEvenly(2) + input(PtCash.PROGRAM_ID) { inState } + for (i in 0..1) output(PtCash.PROGRAM_ID) { outState.copy(amount = splits2[i]).editDepositRef(i.toByte()) } + command(ALICE_PUBKEY) { PtCash.Commands.Move() } + this `fails with` "the amounts balance" + } + // Can't mix currencies. + transaction { + attachment(PtCash.PROGRAM_ID) + input(PtCash.PROGRAM_ID) { inState } + output(PtCash.PROGRAM_ID) { outState.copy(amount = 800.DOLLARS `issued by` defaultIssuer) } + output(PtCash.PROGRAM_ID) { outState.copy(amount = 200.POUNDS `issued by` defaultIssuer) } + command(ALICE_PUBKEY) { PtCash.Commands.Move() } + this `fails with` "the amounts balance" + } + transaction { + attachment(PtCash.PROGRAM_ID) + input(PtCash.PROGRAM_ID) { inState } + input(PtCash.PROGRAM_ID) { + inState.copy( + amount = 150.POUNDS `issued by` defaultIssuer, + owner = AnonymousParty(BOB_PUBKEY) + ) + } + output(PtCash.PROGRAM_ID) { outState.copy(amount = 1150.DOLLARS `issued by` defaultIssuer) } + command(ALICE_PUBKEY) { PtCash.Commands.Move() } + this `fails with` "the amounts balance" + } + // Can't have superfluous input states from different issuers. + transaction { + attachment(PtCash.PROGRAM_ID) + input(PtCash.PROGRAM_ID) { inState } + input(PtCash.PROGRAM_ID) { inState `issued by` MINI_CORP } + output(PtCash.PROGRAM_ID) { outState } + command(ALICE_PUBKEY) { PtCash.Commands.Move() } + this `fails with` "the amounts balance" + } + // Can't combine two different deposits at the same issuer. + transaction { + attachment(PtCash.PROGRAM_ID) + input(PtCash.PROGRAM_ID) { inState } + input(PtCash.PROGRAM_ID) { inState.editDepositRef(3) } + output(PtCash.PROGRAM_ID) { outState.copy(amount = inState.amount * 2).editDepositRef(3) } + command(ALICE_PUBKEY) { PtCash.Commands.Move() } + this `fails with` "for reference [01]" + } + } + + @Test + fun exitLedger() { + // Single input/output straightforward case. + transaction { + attachment(PtCash.PROGRAM_ID) + input(PtCash.PROGRAM_ID) { issuerInState } + output(PtCash.PROGRAM_ID) { issuerInState.copy(amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)) } + + tweak { + command(MEGA_CORP_PUBKEY) { PtCash.Commands.Exit(100.DOLLARS `issued by` defaultIssuer) } + command(MEGA_CORP_PUBKEY) { PtCash.Commands.Move() } + this `fails with` "the amounts balance" + } + + tweak { + command(MEGA_CORP_PUBKEY) { PtCash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) } + this `fails with` "required net.corda.ptflows.contracts.asset.PtCash.Commands.Move command" + + tweak { + command(MEGA_CORP_PUBKEY) { PtCash.Commands.Move() } + this.verifies() + } + } + } + } + + @Test + fun `exit ledger with multiple issuers`() { + // Multi-issuer case. + transaction { + attachment(PtCash.PROGRAM_ID) + input(PtCash.PROGRAM_ID) { issuerInState } + input(PtCash.PROGRAM_ID) { issuerInState.copy(owner = MINI_CORP) `issued by` MINI_CORP } + + output(PtCash.PROGRAM_ID) { issuerInState.copy(amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)) `issued by` MINI_CORP } + output(PtCash.PROGRAM_ID) { issuerInState.copy(owner = MINI_CORP, amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)) } + + command(MEGA_CORP_PUBKEY, MINI_CORP_PUBKEY) { PtCash.Commands.Move() } + + this `fails with` "the amounts balance" + + command(MEGA_CORP_PUBKEY) { PtCash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) } + this `fails with` "the amounts balance" + + command(MINI_CORP_PUBKEY) { PtCash.Commands.Exit(200.DOLLARS `issued by` MINI_CORP.ref(defaultRef)) } + this.verifies() + } + } + + @Test + fun `exit cash not held by its issuer`() { + // Single input/output straightforward case. + transaction { + attachment(PtCash.PROGRAM_ID) + input(PtCash.PROGRAM_ID) { inState } + output(PtCash.PROGRAM_ID) { outState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) } + command(MEGA_CORP_PUBKEY) { PtCash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) } + command(ALICE_PUBKEY) { PtCash.Commands.Move() } + this `fails with` "the amounts balance" + } + } + + @Test + fun multiIssuer() { + transaction { + attachment(PtCash.PROGRAM_ID) + // Gather 2000 dollars from two different issuers. + input(PtCash.PROGRAM_ID) { inState } + input(PtCash.PROGRAM_ID) { inState `issued by` MINI_CORP } + command(ALICE_PUBKEY) { PtCash.Commands.Move() } + + // Can't merge them together. + tweak { + output(PtCash.PROGRAM_ID) { inState.copy(owner = AnonymousParty(BOB_PUBKEY), amount = 2000.DOLLARS `issued by` defaultIssuer) } + this `fails with` "the amounts balance" + } + // Missing MiniCorp deposit + tweak { + output(PtCash.PROGRAM_ID) { inState.copy(owner = AnonymousParty(BOB_PUBKEY)) } + output(PtCash.PROGRAM_ID) { inState.copy(owner = AnonymousParty(BOB_PUBKEY)) } + this `fails with` "the amounts balance" + } + + // This works. + output(PtCash.PROGRAM_ID) { inState.copy(owner = AnonymousParty(BOB_PUBKEY)) } + output(PtCash.PROGRAM_ID) { inState.copy(owner = AnonymousParty(BOB_PUBKEY)) `issued by` MINI_CORP } + this.verifies() + } + } + + @Test + fun multiCurrency() { + // Check we can do an atomic currency trade tx. + transaction { + attachment(PtCash.PROGRAM_ID) + val pounds = PtCash.State(658.POUNDS `issued by` MINI_CORP.ref(3, 4, 5), AnonymousParty(BOB_PUBKEY)) + input(PtCash.PROGRAM_ID) { inState `owned by` AnonymousParty(ALICE_PUBKEY) } + input(PtCash.PROGRAM_ID) { pounds } + output(PtCash.PROGRAM_ID) { inState `owned by` AnonymousParty(BOB_PUBKEY) } + output(PtCash.PROGRAM_ID) { pounds `owned by` AnonymousParty(ALICE_PUBKEY) } + command(ALICE_PUBKEY, BOB_PUBKEY) { PtCash.Commands.Move() } + + this.verifies() + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + // Spend tx generation + + val OUR_KEY: KeyPair by lazy { generateKeyPair() } + val OUR_IDENTITY_1: AbstractParty get() = AnonymousParty(OUR_KEY.public) + + val THEIR_IDENTITY_1 = AnonymousParty(MINI_CORP_PUBKEY) + val THEIR_IDENTITY_2 = AnonymousParty(CHARLIE_PUBKEY) + + fun makeCash(amount: Amount, corp: Party, depositRef: Byte = 1) = + StateAndRef( + TransactionState(PtCash.State(amount `issued by` corp.ref(depositRef), OUR_IDENTITY_1), PtCash.PROGRAM_ID, DUMMY_NOTARY), + StateRef(SecureHash.randomSHA256(), Random().nextInt(32)) + ) + + val WALLET = listOf( + makeCash(100.DOLLARS, MEGA_CORP), + makeCash(400.DOLLARS, MEGA_CORP), + makeCash(80.DOLLARS, MINI_CORP), + makeCash(80.SWISS_FRANCS, MINI_CORP, 2) + ) + + /** + * Generate an exit transaction, removing some amount of cash from the ledger. + */ + private fun makeExit(amount: Amount, corp: Party, depositRef: Byte = 1): WireTransaction { + val tx = TransactionBuilder(DUMMY_NOTARY) + PtCash().generateExit(tx, Amount(amount.quantity, Issued(corp.ref(depositRef), amount.token)), WALLET) + return tx.toWireTransaction(miniCorpServices) + } + + private fun makeSpend(amount: Amount, dest: AbstractParty): WireTransaction { + val tx = TransactionBuilder(DUMMY_NOTARY) + database.transaction { + PtCash.generateSpend(miniCorpServices, tx, amount, dest) + } + return tx.toWireTransaction(miniCorpServices) + } + + /** + * Try exiting an amount which matches a single state. + */ + @Test + fun generateSimpleExit() { + initialiseTestSerialization() + val wtx = makeExit(100.DOLLARS, MEGA_CORP, 1) + assertEquals(WALLET[0].ref, wtx.inputs[0]) + assertEquals(0, wtx.outputs.size) + + val expectedMove = PtCash.Commands.Move() + val expectedExit = PtCash.Commands.Exit(Amount(10000, Issued(MEGA_CORP.ref(1), USD))) + + assertEquals(listOf(expectedMove, expectedExit), wtx.commands.map { it.value }) + } + + /** + * Try exiting an amount smaller than the smallest available input state, and confirm change is generated correctly. + */ + @Test + fun generatePartialExit() { + initialiseTestSerialization() + val wtx = makeExit(50.DOLLARS, MEGA_CORP, 1) + assertEquals(WALLET[0].ref, wtx.inputs[0]) + assertEquals(1, wtx.outputs.size) + assertEquals(WALLET[0].state.data.copy(amount = WALLET[0].state.data.amount.splitEvenly(2).first()), wtx.getOutput(0)) + } + + /** + * Try exiting a currency we don't have. + */ + @Test + fun generateAbsentExit() { + initialiseTestSerialization() + assertFailsWith { makeExit(100.POUNDS, MEGA_CORP, 1) } + } + + /** + * Try exiting with a reference mis-match. + */ + @Test + fun generateInvalidReferenceExit() { + initialiseTestSerialization() + assertFailsWith { makeExit(100.POUNDS, MEGA_CORP, 2) } + } + + /** + * Try exiting an amount greater than the maximum available. + */ + @Test + fun generateInsufficientExit() { + initialiseTestSerialization() + assertFailsWith { makeExit(1000.DOLLARS, MEGA_CORP, 1) } + } + + /** + * Try exiting for an owner with no states + */ + @Test + fun generateOwnerWithNoStatesExit() { + initialiseTestSerialization() + assertFailsWith { makeExit(100.POUNDS, CHARLIE, 1) } + } + + /** + * Try exiting when vault is empty + */ + @Test + fun generateExitWithEmptyVault() { + initialiseTestSerialization() + assertFailsWith { + val tx = TransactionBuilder(DUMMY_NOTARY) + PtCash().generateExit(tx, Amount(100, Issued(CHARLIE.ref(1), GBP)), emptyList()) + } + } + + @Test + fun generateSimpleDirectSpend() { + initialiseTestSerialization() + val wtx = + database.transaction { + makeSpend(100.DOLLARS, THEIR_IDENTITY_1) + } + database.transaction { + @Suppress("UNCHECKED_CAST") + val vaultState = vaultStatesUnconsumed.elementAt(0) + assertEquals(vaultState.ref, wtx.inputs[0]) + assertEquals(vaultState.state.data.copy(owner = THEIR_IDENTITY_1), wtx.getOutput(0)) + assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is PtCash.Commands.Move }.signers[0]) + } + } + + @Test + fun generateSimpleSpendWithParties() { + initialiseTestSerialization() + database.transaction { + + val tx = TransactionBuilder(DUMMY_NOTARY) + PtCash.generateSpend(miniCorpServices, tx, 80.DOLLARS, ALICE, setOf(MINI_CORP)) + + assertEquals(vaultStatesUnconsumed.elementAt(2).ref, tx.inputStates()[0]) + } + } + + @Test + fun generateSimpleSpendWithChange() { + initialiseTestSerialization() + val wtx = + database.transaction { + makeSpend(10.DOLLARS, THEIR_IDENTITY_1) + } + database.transaction { + @Suppress("UNCHECKED_CAST") + val vaultState = vaultStatesUnconsumed.elementAt(0) + val changeAmount = 90.DOLLARS `issued by` defaultIssuer + val likelyChangeState = wtx.outputs.map(TransactionState<*>::data).filter { state -> + if (state is PtCash.State) { + state.amount == changeAmount + } else { + false + } + }.single() + val changeOwner = (likelyChangeState as PtCash.State).owner + assertEquals(1, miniCorpServices.keyManagementService.filterMyKeys(setOf(changeOwner.owningKey)).toList().size) + assertEquals(vaultState.ref, wtx.inputs[0]) + assertEquals(vaultState.state.data.copy(owner = THEIR_IDENTITY_1, amount = 10.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data) + assertEquals(vaultState.state.data.copy(amount = changeAmount, owner = changeOwner), wtx.outputs[1].data) + assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is PtCash.Commands.Move }.signers[0]) + } + } + + @Test + fun generateSpendWithTwoInputs() { + initialiseTestSerialization() + val wtx = + database.transaction { + makeSpend(500.DOLLARS, THEIR_IDENTITY_1) + } + database.transaction { + @Suppress("UNCHECKED_CAST") + val vaultState0 = vaultStatesUnconsumed.elementAt(0) + val vaultState1 = vaultStatesUnconsumed.elementAt(1) + assertEquals(vaultState0.ref, wtx.inputs[0]) + assertEquals(vaultState1.ref, wtx.inputs[1]) + assertEquals(vaultState0.state.data.copy(owner = THEIR_IDENTITY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.getOutput(0)) + assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is PtCash.Commands.Move }.signers[0]) + } + } + + @Test + fun generateSpendMixedDeposits() { + initialiseTestSerialization() + val wtx = + database.transaction { + val wtx = makeSpend(580.DOLLARS, THEIR_IDENTITY_1) + assertEquals(3, wtx.inputs.size) + wtx + } + database.transaction { + val vaultState0: StateAndRef = vaultStatesUnconsumed.elementAt(0) + val vaultState1: StateAndRef = vaultStatesUnconsumed.elementAt(1) + val vaultState2: StateAndRef = vaultStatesUnconsumed.elementAt(2) + assertEquals(vaultState0.ref, wtx.inputs[0]) + assertEquals(vaultState1.ref, wtx.inputs[1]) + assertEquals(vaultState2.ref, wtx.inputs[2]) + assertEquals(vaultState0.state.data.copy(owner = THEIR_IDENTITY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[1].data) + assertEquals(vaultState2.state.data.copy(owner = THEIR_IDENTITY_1), wtx.outputs[0].data) + assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is PtCash.Commands.Move }.signers[0]) + } + } + + @Test + fun generateSpendInsufficientBalance() { + initialiseTestSerialization() + database.transaction { + + val e: InsufficientBalanceException = assertFailsWith("balance") { + makeSpend(1000.DOLLARS, THEIR_IDENTITY_1) + } + assertEquals((1000 - 580).DOLLARS, e.amountMissing) + + assertFailsWith(InsufficientBalanceException::class) { + makeSpend(81.SWISS_FRANCS, THEIR_IDENTITY_1) + } + } + } + + /** + * Confirm that aggregation of states is correctly modelled. + */ + @Test + fun aggregation() { + val fiveThousandDollarsFromMega = PtCash.State(5000.DOLLARS `issued by` MEGA_CORP.ref(2), MEGA_CORP) + val twoThousandDollarsFromMega = PtCash.State(2000.DOLLARS `issued by` MEGA_CORP.ref(2), MINI_CORP) + val oneThousandDollarsFromMini = PtCash.State(1000.DOLLARS `issued by` MINI_CORP.ref(3), MEGA_CORP) + + // Obviously it must be possible to aggregate states with themselves + assertEquals(fiveThousandDollarsFromMega.amount.token, fiveThousandDollarsFromMega.amount.token) + + // Owner is not considered when calculating whether it is possible to aggregate states + assertEquals(fiveThousandDollarsFromMega.amount.token, twoThousandDollarsFromMega.amount.token) + + // States cannot be aggregated if the deposit differs + assertNotEquals(fiveThousandDollarsFromMega.amount.token, oneThousandDollarsFromMini.amount.token) + assertNotEquals(twoThousandDollarsFromMega.amount.token, oneThousandDollarsFromMini.amount.token) + + // States cannot be aggregated if the currency differs + assertNotEquals(oneThousandDollarsFromMini.amount.token, + PtCash.State(1000.POUNDS `issued by` MINI_CORP.ref(3), MEGA_CORP).amount.token) + + // States cannot be aggregated if the reference differs + assertNotEquals(fiveThousandDollarsFromMega.amount.token, (fiveThousandDollarsFromMega `with deposit` defaultIssuer).amount.token) + assertNotEquals((fiveThousandDollarsFromMega `with deposit` defaultIssuer).amount.token, fiveThousandDollarsFromMega.amount.token) + } + + @Test + fun `summing by owner`() { + val states = listOf( + PtCash.State(1000.DOLLARS `issued by` defaultIssuer, MINI_CORP), + PtCash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP), + PtCash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP) + ) + assertEquals(6000.DOLLARS `issued by` defaultIssuer, states.sumCashBy(MEGA_CORP)) + } + + @Test(expected = UnsupportedOperationException::class) + fun `summing by owner throws`() { + val states = listOf( + PtCash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP), + PtCash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP) + ) + states.sumCashBy(MINI_CORP) + } + + @Test + fun `summing no currencies`() { + val states = emptyList() + assertEquals(0.POUNDS `issued by` defaultIssuer, states.sumCashOrZero(GBP `issued by` defaultIssuer)) + assertNull(states.sumCashOrNull()) + } + + @Test(expected = UnsupportedOperationException::class) + fun `summing no currencies throws`() { + val states = emptyList() + states.sumCash() + } + + @Test + fun `summing a single currency`() { + val states = listOf( + PtCash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP), + PtCash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP), + PtCash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP) + ) + // Test that summing everything produces the total number of dollars + val expected = 7000.DOLLARS `issued by` defaultIssuer + val actual = states.sumCash() + assertEquals(expected, actual) + } + + @Test(expected = IllegalArgumentException::class) + fun `summing multiple currencies`() { + val states = listOf( + PtCash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP), + PtCash.State(4000.POUNDS `issued by` defaultIssuer, MEGA_CORP) + ) + // Test that summing everything fails because we're mixing units + states.sumCash() + } + + // Double spend. + @Test + fun chainCashDoubleSpendFailsWith() { + val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), MEGA_CORP_KEY) + + ledger(mockService) { + unverifiedTransaction { + attachment(PtCash.PROGRAM_ID) + output(PtCash.PROGRAM_ID, "MEGA_CORP cash") { + PtCash.State( + amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1), + owner = MEGA_CORP + ) + } + } + + transaction { + attachment(PtCash.PROGRAM_ID) + input("MEGA_CORP cash") + output(PtCash.PROGRAM_ID, "MEGA_CORP cash 2", "MEGA_CORP cash".output().copy(owner = AnonymousParty(ALICE_PUBKEY)) ) + command(MEGA_CORP_PUBKEY) { PtCash.Commands.Move() } + this.verifies() + } + + tweak { + transaction { + attachment(PtCash.PROGRAM_ID) + input("MEGA_CORP cash") + // We send it to another pubkey so that the transaction is not identical to the previous one + output(PtCash.PROGRAM_ID, "MEGA_CORP cash 3", "MEGA_CORP cash".output().copy(owner = ALICE)) + command(MEGA_CORP_PUBKEY) { PtCash.Commands.Move() } + this.verifies() + } + this.fails() + } + + this.verifies() + } + } + + @Test + fun multiSpend() { + initialiseTestSerialization() + val tx = TransactionBuilder(DUMMY_NOTARY) + database.transaction { + val payments = listOf( + PartyAndAmount(THEIR_IDENTITY_1, 400.DOLLARS), + PartyAndAmount(THEIR_IDENTITY_2, 150.DOLLARS) + ) + PtCash.generateSpend(miniCorpServices, tx, payments) + } + val wtx = tx.toWireTransaction(miniCorpServices) + fun out(i: Int) = wtx.getOutput(i) as PtCash.State + assertEquals(4, wtx.outputs.size) + assertEquals(80.DOLLARS, out(0).amount.withoutIssuer()) + assertEquals(320.DOLLARS, out(1).amount.withoutIssuer()) + assertEquals(150.DOLLARS, out(2).amount.withoutIssuer()) + assertEquals(30.DOLLARS, out(3).amount.withoutIssuer()) + assertEquals(MINI_CORP, out(0).amount.token.issuer.party) + assertEquals(MEGA_CORP, out(1).amount.token.issuer.party) + assertEquals(MEGA_CORP, out(2).amount.token.issuer.party) + assertEquals(MEGA_CORP, out(3).amount.token.issuer.party) + } +} From 1cb4f566096f3e3b1aacc6c7f8da75ce24457615 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Tue, 10 Oct 2017 14:58:55 +0100 Subject: [PATCH 03/15] Added unit tests for commercial paper --- .../contracts/JavaPtCommercialPaper.java | 267 +++++++++++++++ .../contracts/PtCommercialPaperTests.kt | 317 ++++++++++++++++++ .../asset/PtCashTests.kt | 3 +- 3 files changed, 585 insertions(+), 2 deletions(-) create mode 100644 perftestflows/src/main/java/net/corda/ptflows/contracts/JavaPtCommercialPaper.java create mode 100644 perftestflows/src/test/kotlin/net/corda/ptflows/contracts/PtCommercialPaperTests.kt rename perftestflows/src/test/kotlin/net/corda/ptflows/{contract => contracts}/asset/PtCashTests.kt (99%) diff --git a/perftestflows/src/main/java/net/corda/ptflows/contracts/JavaPtCommercialPaper.java b/perftestflows/src/main/java/net/corda/ptflows/contracts/JavaPtCommercialPaper.java new file mode 100644 index 0000000000..ce9c2d263f --- /dev/null +++ b/perftestflows/src/main/java/net/corda/ptflows/contracts/JavaPtCommercialPaper.java @@ -0,0 +1,267 @@ +package net.corda.ptflows.contracts; + + +import co.paralleluniverse.fibers.Suspendable; +import kotlin.Unit; +import net.corda.core.contracts.*; +import net.corda.core.crypto.NullKeys.NullPublicKey; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.AnonymousParty; +import net.corda.core.identity.Party; +import net.corda.core.identity.PartyAndCertificate; +import net.corda.core.node.ServiceHub; +import net.corda.core.transactions.LedgerTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.ptflows.contracts.asset.PtCash; +import net.corda.ptflows.utils.StateSummingUtilitiesKt; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.time.Instant; +import java.util.Collections; +import java.util.Currency; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; + +import static net.corda.core.contracts.ContractsDSL.requireSingleCommand; +import static net.corda.core.contracts.ContractsDSL.requireThat; + +/** + * This is a Java version of the CommercialPaper contract (chosen because it's simple). This demonstrates how the + * use of Kotlin for implementation of the framework does not impose the same language choice on contract developers. + */ +@SuppressWarnings("unused") +public class JavaPtCommercialPaper implements Contract { + static final String JCP_PROGRAM_ID = "net.corda.ptflows.contracts.JavaPtCommercialPaper"; + + @SuppressWarnings("unused") + public static class State implements OwnableState, IPtCommercialPaperState { + private PartyAndReference issuance; + private AbstractParty owner; + private Amount> faceValue; + private Instant maturityDate; + + public State() { + } // For serialization + + public State(PartyAndReference issuance, AbstractParty owner, Amount> faceValue, + Instant maturityDate) { + this.issuance = issuance; + this.owner = owner; + this.faceValue = faceValue; + this.maturityDate = maturityDate; + } + + public State copy() { + return new State(this.issuance, this.owner, this.faceValue, this.maturityDate); + } + + public IPtCommercialPaperState withOwner(AbstractParty newOwner) { + return new State(this.issuance, newOwner, this.faceValue, this.maturityDate); + } + + @NotNull + @Override + public CommandAndState withNewOwner(@NotNull AbstractParty newOwner) { + return new CommandAndState(new Commands.Move(), new State(this.issuance, newOwner, this.faceValue, this.maturityDate)); + } + + public IPtCommercialPaperState withFaceValue(Amount> newFaceValue) { + return new State(this.issuance, this.owner, newFaceValue, this.maturityDate); + } + + public IPtCommercialPaperState withMaturityDate(Instant newMaturityDate) { + return new State(this.issuance, this.owner, this.faceValue, newMaturityDate); + } + + public PartyAndReference getIssuance() { + return issuance; + } + + @NotNull + public AbstractParty getOwner() { + return owner; + } + + public Amount> getFaceValue() { + return faceValue; + } + + public Instant getMaturityDate() { + return maturityDate; + } + + @Override + public boolean equals(Object that) { + if (this == that) return true; + if (that == null || getClass() != that.getClass()) return false; + + State state = (State) that; + + if (issuance != null ? !issuance.equals(state.issuance) : state.issuance != null) return false; + if (owner != null ? !owner.equals(state.owner) : state.owner != null) return false; + if (faceValue != null ? !faceValue.equals(state.faceValue) : state.faceValue != null) return false; + if (maturityDate != null ? !maturityDate.equals(state.maturityDate) : state.maturityDate != null) + return false; + return true; + } + + @Override + public int hashCode() { + int result = issuance != null ? issuance.hashCode() : 0; + result = 31 * result + (owner != null ? owner.hashCode() : 0); + result = 31 * result + (faceValue != null ? faceValue.hashCode() : 0); + result = 31 * result + (maturityDate != null ? maturityDate.hashCode() : 0); + return result; + } + + State withoutOwner() { + return new State(issuance, new AnonymousParty(NullPublicKey.INSTANCE), faceValue, maturityDate); + } + + @NotNull + @Override + public List getParticipants() { + return Collections.singletonList(this.owner); + } + } + + public interface Commands extends CommandData { + class Move implements Commands { + @Override + public boolean equals(Object obj) { + return obj instanceof Move; + } + } + + class Redeem implements Commands { + @Override + public boolean equals(Object obj) { + return obj instanceof Redeem; + } + } + + class Issue implements Commands { + @Override + public boolean equals(Object obj) { + return obj instanceof Issue; + } + } + } + + @NotNull + private List> extractCommands(@NotNull LedgerTransaction tx) { + return tx.getCommands() + .stream() + .filter((CommandWithParties command) -> command.getValue() instanceof Commands) + .map((CommandWithParties command) -> new CommandWithParties<>(command.getSigners(), command.getSigningParties(), (Commands) command.getValue())) + .collect(Collectors.toList()); + } + + @Override + public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { + + // Group by everything except owner: any modification to the CP at all is considered changing it fundamentally. + final List> groups = tx.groupStates(State.class, State::withoutOwner); + + // There are two possible things that can be done with this CP. The first is trading it. The second is redeeming + // it for cash on or after the maturity date. + final List> commands = tx.getCommands().stream().filter( + it -> it.getValue() instanceof Commands + ).collect(Collectors.toList()); + final CommandWithParties command = onlyElementOf(commands); + final TimeWindow timeWindow = tx.getTimeWindow(); + + for (final LedgerTransaction.InOutGroup group : groups) { + final List inputs = group.getInputs(); + final List outputs = group.getOutputs(); + if (command.getValue() instanceof Commands.Move) { + final CommandWithParties cmd = requireSingleCommand(tx.getCommands(), Commands.Move.class); + // There should be only a single input due to aggregation above + final State input = onlyElementOf(inputs); + + if (!cmd.getSigners().contains(input.getOwner().getOwningKey())) + throw new IllegalStateException("Failed requirement: the transaction is signed by the owner of the CP"); + + // Check the output CP state is the same as the input state, ignoring the owner field. + if (outputs.size() != 1) { + throw new IllegalStateException("the state is propagated"); + } + } else if (command.getValue() instanceof Commands.Redeem) { + final CommandWithParties cmd = requireSingleCommand(tx.getCommands(), Commands.Redeem.class); + + // There should be only a single input due to aggregation above + final State input = onlyElementOf(inputs); + + if (!cmd.getSigners().contains(input.getOwner().getOwningKey())) + throw new IllegalStateException("Failed requirement: the transaction is signed by the owner of the CP"); + + final Instant time = null == timeWindow + ? null + : timeWindow.getUntilTime(); + final Amount> received = StateSummingUtilitiesKt.sumCashBy(tx.getOutputStates(), input.getOwner()); + + requireThat(require -> { + require.using("must be timestamped", timeWindow != null); + require.using("received amount equals the face value: " + + received + " vs " + input.getFaceValue(), received.equals(input.getFaceValue())); + require.using("the paper must have matured", time != null && !time.isBefore(input.getMaturityDate())); + require.using("the received amount equals the face value", input.getFaceValue().equals(received)); + require.using("the paper must be destroyed", outputs.isEmpty()); + return Unit.INSTANCE; + }); + } else if (command.getValue() instanceof Commands.Issue) { + final CommandWithParties cmd = requireSingleCommand(tx.getCommands(), Commands.Issue.class); + final State output = onlyElementOf(outputs); + final Instant time = null == timeWindow + ? null + : timeWindow.getUntilTime(); + + requireThat(require -> { + require.using("output values sum to more than the inputs", inputs.isEmpty()); + require.using("output values sum to more than the inputs", output.faceValue.getQuantity() > 0); + require.using("must be timestamped", timeWindow != null); + require.using("the maturity date is not in the past", time != null && time.isBefore(output.getMaturityDate())); + require.using("output states are issued by a command signer", cmd.getSigners().contains(output.issuance.getParty().getOwningKey())); + return Unit.INSTANCE; + }); + } + } + } + + public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount> faceValue, @Nullable Instant maturityDate, @NotNull Party notary, Integer encumbrance) { + State state = new State(issuance, issuance.getParty(), faceValue, maturityDate); + TransactionState output = new TransactionState<>(state, JCP_PROGRAM_ID, notary, encumbrance); + return new TransactionBuilder(notary).withItems(output, new Command<>(new Commands.Issue(), issuance.getParty().getOwningKey())); + } + + public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount> faceValue, @Nullable Instant maturityDate, @NotNull Party notary) { + return generateIssue(issuance, faceValue, maturityDate, notary, null); + } + + @Suspendable + public void generateRedeem(final TransactionBuilder tx, + final StateAndRef paper, + final ServiceHub services, + final PartyAndCertificate ourIdentity) throws InsufficientBalanceException { + PtCash.generateSpend(services, tx, Structures.withoutIssuer(paper.getState().getData().getFaceValue()), ourIdentity, paper.getState().getData().getOwner(), Collections.emptySet()); + tx.addInputState(paper); + tx.addCommand(new Command<>(new Commands.Redeem(), paper.getState().getData().getOwner().getOwningKey())); + } + + public void generateMove(TransactionBuilder tx, StateAndRef paper, AbstractParty newOwner) { + tx.addInputState(paper); + tx.addOutputState(new TransactionState<>(new State(paper.getState().getData().getIssuance(), newOwner, paper.getState().getData().getFaceValue(), paper.getState().getData().getMaturityDate()), JCP_PROGRAM_ID, paper.getState().getNotary(), paper.getState().getEncumbrance())); + tx.addCommand(new Command<>(new Commands.Move(), paper.getState().getData().getOwner().getOwningKey())); + } + + private static T onlyElementOf(Iterable iterable) { + Iterator iter = iterable.iterator(); + T item = iter.next(); + if (iter.hasNext()) { + throw new IllegalArgumentException("Iterable has more than one element!"); + } + return item; + } +} diff --git a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/PtCommercialPaperTests.kt b/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/PtCommercialPaperTests.kt new file mode 100644 index 0000000000..d6b11c698b --- /dev/null +++ b/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/PtCommercialPaperTests.kt @@ -0,0 +1,317 @@ +package net.corda.ptflows.contracts + +import net.corda.core.contracts.* +import net.corda.core.identity.AnonymousParty +import net.corda.core.identity.Party +import net.corda.core.node.services.Vault +import net.corda.core.node.services.VaultService +import net.corda.core.transactions.SignedTransaction +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.days +import net.corda.core.utilities.seconds +import net.corda.finance.DOLLARS +import net.corda.finance.`issued by` +import net.corda.ptflows.contracts.asset.* +import net.corda.testing.* +import net.corda.ptflows.contracts.asset.fillWithSomeTestCash +import net.corda.testing.node.MockServices +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import java.time.Instant +import java.util.* +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +// TODO: The generate functions aren't tested by these tests: add them. + +interface IPtCommercialPaperTestTemplate { + fun getPaper(): IPtCommercialPaperState + fun getIssueCommand(notary: Party): CommandData + fun getRedeemCommand(notary: Party): CommandData + fun getMoveCommand(): CommandData + fun getContract(): ContractClassName +} + +class JavaCommercialPaperTest : IPtCommercialPaperTestTemplate { + override fun getPaper(): IPtCommercialPaperState = JavaPtCommercialPaper.State( + MEGA_CORP.ref(123), + MEGA_CORP, + 1000.DOLLARS `issued by` MEGA_CORP.ref(123), + TEST_TX_TIME + 7.days + ) + + override fun getIssueCommand(notary: Party): CommandData = JavaPtCommercialPaper.Commands.Issue() + override fun getRedeemCommand(notary: Party): CommandData = JavaPtCommercialPaper.Commands.Redeem() + override fun getMoveCommand(): CommandData = JavaPtCommercialPaper.Commands.Move() + override fun getContract() = JavaPtCommercialPaper.JCP_PROGRAM_ID +} + +class KotlinCommercialPaperTest : IPtCommercialPaperTestTemplate { + override fun getPaper(): IPtCommercialPaperState = PtCommercialPaper.State( + issuance = MEGA_CORP.ref(123), + owner = MEGA_CORP, + faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123), + maturityDate = TEST_TX_TIME + 7.days + ) + + override fun getIssueCommand(notary: Party): CommandData = PtCommercialPaper.Commands.Issue() + override fun getRedeemCommand(notary: Party): CommandData = PtCommercialPaper.Commands.Redeem() + override fun getMoveCommand(): CommandData = PtCommercialPaper.Commands.Move() + override fun getContract() = PtCommercialPaper.CP_PROGRAM_ID +} + +class KotlinCommercialPaperLegacyTest : IPtCommercialPaperTestTemplate { + override fun getPaper(): IPtCommercialPaperState = PtCommercialPaper.State( + issuance = MEGA_CORP.ref(123), + owner = MEGA_CORP, + faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123), + maturityDate = TEST_TX_TIME + 7.days + ) + + override fun getIssueCommand(notary: Party): CommandData = PtCommercialPaper.Commands.Issue() + override fun getRedeemCommand(notary: Party): CommandData = PtCommercialPaper.Commands.Redeem() + override fun getMoveCommand(): CommandData = PtCommercialPaper.Commands.Move() + override fun getContract() = PtCommercialPaper.CP_PROGRAM_ID +} + +@RunWith(Parameterized::class) +class CommercialPaperTestsGeneric { + companion object { + @Parameterized.Parameters @JvmStatic + fun data() = listOf(JavaCommercialPaperTest(), KotlinCommercialPaperTest(), KotlinCommercialPaperLegacyTest()) + } + + @Parameterized.Parameter + lateinit var thisTest: IPtCommercialPaperTestTemplate + + val issuer = MEGA_CORP.ref(123) + + @Test + fun `trade lifecycle test`() { + val someProfits = 1200.DOLLARS `issued by` issuer + ledger { + unverifiedTransaction { + attachment(PtCash.PROGRAM_ID) + output(PtCash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE) + output(PtCash.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, JavaPtCommercialPaper.JCP_PROGRAM_ID) + output(thisTest.getContract(), "paper") { thisTest.getPaper() } + command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) } + timeWindow(TEST_TX_TIME) + this.verifies() + } + + // 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(PtCash.PROGRAM_ID, JavaPtCommercialPaper.JCP_PROGRAM_ID) + input("paper") + input("alice's $900") + output(PtCash.PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP } + output(thisTest.getContract(), "alice's paper") { "paper".output().withOwner(ALICE) } + command(ALICE_PUBKEY) { PtCash.Commands.Move() } + command(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() } + this.verifies() + } + + // 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, JavaPtCommercialPaper.JCP_PROGRAM_ID) + input("alice's paper") + input("some profits") + + fun TransactionDSL.outputs(aliceGetsBack: Amount>) { + output(PtCash.PROGRAM_ID, "Alice's profit") { aliceGetsBack.STATE `owned by` ALICE } + output(PtCash.PROGRAM_ID, "Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP } + } + + command(MEGA_CORP_PUBKEY) { PtCash.Commands.Move() } + command(ALICE_PUBKEY) { thisTest.getRedeemCommand(DUMMY_NOTARY) } + + tweak { + outputs(700.DOLLARS `issued by` issuer) + timeWindow(TEST_TX_TIME + 8.days) + this `fails with` "received amount equals the face value" + } + outputs(1000.DOLLARS `issued by` issuer) + + + tweak { + timeWindow(TEST_TX_TIME + 2.days) + this `fails with` "must have matured" + } + timeWindow(TEST_TX_TIME + 8.days) + + tweak { + output(thisTest.getContract()) { "paper".output() } + this `fails with` "must be destroyed" + } + + this.verifies() + } + } + } + + @Test + fun `key mismatch at issue`() { + transaction { + attachment(CP_PROGRAM_ID) + attachment(JavaPtCommercialPaper.JCP_PROGRAM_ID) + output(thisTest.getContract()) { thisTest.getPaper() } + command(MINI_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) } + timeWindow(TEST_TX_TIME) + this `fails with` "output states are issued by a command signer" + } + } + + @Test + fun `face value is not zero`() { + transaction { + attachment(CP_PROGRAM_ID) + attachment(JavaPtCommercialPaper.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) + this `fails with` "output values sum to more than the inputs" + } + } + + @Test + fun `maturity date not in the past`() { + transaction { + attachment(CP_PROGRAM_ID) + attachment(JavaPtCommercialPaper.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) + this `fails with` "maturity date is not in the past" + } + } + + @Test + fun `issue cannot replace an existing state`() { + transaction { + attachment(CP_PROGRAM_ID) + attachment(JavaPtCommercialPaper.JCP_PROGRAM_ID) + input(thisTest.getContract(), thisTest.getPaper()) + output(thisTest.getContract()) { thisTest.getPaper() } + command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) } + timeWindow(TEST_TX_TIME) + this `fails with` "output values sum to more than the inputs" + } + } + + /** + * Unit test requires two separate Database instances to represent each of the two + * transaction participants (enforces uniqueness of vault content in lieu of partipant identity) + */ + + private lateinit var bigCorpServices: MockServices + private lateinit var bigCorpVault: Vault + private lateinit var bigCorpVaultService: VaultService + + private lateinit var aliceServices: MockServices + private lateinit var aliceVaultService: VaultService + private lateinit var alicesVault: Vault + + private val notaryServices = MockServices(DUMMY_NOTARY_KEY) + private val issuerServices = MockServices(DUMMY_CASH_ISSUER_KEY) + + private lateinit var moveTX: SignedTransaction + + // @Test + @Ignore + fun `issue move and then redeem`() { + setCordappPackages("net.corda.finance.contracts") + initialiseTestSerialization() + val aliceDatabaseAndServices = MockServices.makeTestDatabaseAndMockServices(keys = listOf(ALICE_KEY)) + val databaseAlice = aliceDatabaseAndServices.first + aliceServices = aliceDatabaseAndServices.second + aliceVaultService = aliceServices.vaultService + + databaseAlice.transaction { + alicesVault = aliceServices.fillWithSomeTestCash(9000.DOLLARS, issuerServices, atLeastThisManyStates = 1, atMostThisManyStates = 1, issuedBy = DUMMY_CASH_ISSUER) + aliceVaultService = aliceServices.vaultService + } + + val bigCorpDatabaseAndServices = MockServices.makeTestDatabaseAndMockServices(keys = listOf(BIG_CORP_KEY)) + val databaseBigCorp = bigCorpDatabaseAndServices.first + bigCorpServices = bigCorpDatabaseAndServices.second + bigCorpVaultService = bigCorpServices.vaultService + + databaseBigCorp.transaction { + bigCorpVault = bigCorpServices.fillWithSomeTestCash(13000.DOLLARS, issuerServices, atLeastThisManyStates = 1, atMostThisManyStates = 1, issuedBy = DUMMY_CASH_ISSUER) + bigCorpVaultService = bigCorpServices.vaultService + } + + // Propagate the cash transactions to each side. + aliceServices.recordTransactions(bigCorpVault.states.map { bigCorpServices.validatedTransactions.getTransaction(it.ref.txhash)!! }) + bigCorpServices.recordTransactions(alicesVault.states.map { aliceServices.validatedTransactions.getTransaction(it.ref.txhash)!! }) + + // BigCorpâ„¢ issues $10,000 of commercial paper, to mature in 30 days, owned initially by itself. + val faceValue = 10000.DOLLARS `issued by` DUMMY_CASH_ISSUER + val issuance = bigCorpServices.myInfo.chooseIdentity().ref(1) + val issueBuilder = PtCommercialPaper().generateIssue(issuance, faceValue, TEST_TX_TIME + 30.days, DUMMY_NOTARY) + issueBuilder.setTimeWindow(TEST_TX_TIME, 30.seconds) + val issuePtx = bigCorpServices.signInitialTransaction(issueBuilder) + val issueTx = notaryServices.addSignature(issuePtx) + + databaseAlice.transaction { + // Alice pays $9000 to BigCorp to own some of their debt. + moveTX = run { + val builder = TransactionBuilder(DUMMY_NOTARY) + PtCash.generateSpend(aliceServices, builder, 9000.DOLLARS, AnonymousParty(bigCorpServices.key.public)) + PtCommercialPaper().generateMove(builder, issueTx.tx.outRef(0), AnonymousParty(aliceServices.key.public)) + val ptx = aliceServices.signInitialTransaction(builder) + val ptx2 = bigCorpServices.addSignature(ptx) + val stx = notaryServices.addSignature(ptx2) + stx + } + } + + databaseBigCorp.transaction { + // Verify the txns are valid and insert into both sides. + listOf(issueTx, moveTX).forEach { + it.toLedgerTransaction(aliceServices).verify() + aliceServices.recordTransactions(it) + bigCorpServices.recordTransactions(it) + } + } + + databaseBigCorp.transaction { + fun makeRedeemTX(time: Instant): Pair { + val builder = TransactionBuilder(DUMMY_NOTARY) + builder.setTimeWindow(time, 30.seconds) + PtCommercialPaper().generateRedeem(builder, moveTX.tx.outRef(1), bigCorpServices, bigCorpServices.myInfo.chooseIdentityAndCert()) + val ptx = aliceServices.signInitialTransaction(builder) + val ptx2 = bigCorpServices.addSignature(ptx) + val stx = notaryServices.addSignature(ptx2) + return Pair(stx, builder.lockId) + } + + val redeemTX = makeRedeemTX(TEST_TX_TIME + 10.days) + val tooEarlyRedemption = redeemTX.first + val tooEarlyRedemptionLockId = redeemTX.second + val e = assertFailsWith(TransactionVerificationException::class) { + tooEarlyRedemption.toLedgerTransaction(aliceServices).verify() + } + // manually release locks held by this failing transaction + aliceServices.vaultService.softLockRelease(tooEarlyRedemptionLockId) + assertTrue(e.cause!!.message!!.contains("paper must have matured")) + + val validRedemption = makeRedeemTX(TEST_TX_TIME + 31.days).first + validRedemption.toLedgerTransaction(aliceServices).verify() + // soft lock not released after success either!!! (as transaction not recorded) + } + resetTestSerialization() + } +} + + diff --git a/perftestflows/src/test/kotlin/net/corda/ptflows/contract/asset/PtCashTests.kt b/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/asset/PtCashTests.kt similarity index 99% rename from perftestflows/src/test/kotlin/net/corda/ptflows/contract/asset/PtCashTests.kt rename to perftestflows/src/test/kotlin/net/corda/ptflows/contracts/asset/PtCashTests.kt index 2f75481982..52e7a3fa3e 100644 --- a/perftestflows/src/test/kotlin/net/corda/ptflows/contract/asset/PtCashTests.kt +++ b/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/asset/PtCashTests.kt @@ -1,4 +1,4 @@ -package net.corda.ptflows.contract.asset +package net.corda.ptflows.contracts.asset import net.corda.core.contracts.* @@ -22,7 +22,6 @@ import net.corda.ptflows.utils.sumCashOrNull import net.corda.ptflows.utils.sumCashOrZero import net.corda.node.services.vault.NodeVaultService import net.corda.node.utilities.CordaPersistence -import net.corda.ptflows.contracts.asset.* import net.corda.testing.* import net.corda.testing.contracts.DummyState import net.corda.testing.contracts.calculateRandomlySizedAmounts From e0b684b3ea112f3785975058b6d42f7eb2a7d8eb Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Tue, 10 Oct 2017 16:33:16 +0100 Subject: [PATCH 04/15] Simple trade flow for commercial paper --- .../corda/ptflows/flows/TwoPartyTradeFlow.kt | 242 ++++++ .../contracts/PtCommercialPaperTests.kt | 4 +- .../contracts/flows/TwoPartyTradeFlowTest.kt | 779 ++++++++++++++++++ 3 files changed, 1023 insertions(+), 2 deletions(-) create mode 100644 perftestflows/src/main/kotlin/net/corda/ptflows/flows/TwoPartyTradeFlow.kt create mode 100644 perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/TwoPartyTradeFlowTest.kt diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/TwoPartyTradeFlow.kt b/perftestflows/src/main/kotlin/net/corda/ptflows/flows/TwoPartyTradeFlow.kt new file mode 100644 index 0000000000..0f40dda661 --- /dev/null +++ b/perftestflows/src/main/kotlin/net/corda/ptflows/flows/TwoPartyTradeFlow.kt @@ -0,0 +1,242 @@ +package net.corda.ptflows.flows + +import co.paralleluniverse.fibers.Suspendable +import net.corda.confidential.IdentitySyncFlow +import net.corda.core.contracts.* +import net.corda.core.flows.* +import net.corda.core.identity.Party +import net.corda.core.identity.PartyAndCertificate +import net.corda.core.serialization.CordaSerializable +import net.corda.core.transactions.SignedTransaction +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.ProgressTracker +import net.corda.core.utilities.seconds +import net.corda.core.utilities.unwrap +import net.corda.ptflows.contracts.asset.PtCash +import net.corda.ptflows.utils.sumCashBy +import java.security.PublicKey +import java.util.* + +/** + * This asset trading flow implements a "delivery vs payment" type swap. It has two parties (B and S for buyer + * and seller) and the following steps: + * + * 1. S sends the [StateAndRef] pointing to what they want to sell to B, along with info about the price they require + * B to pay. For example this has probably been agreed on an exchange. + * 2. B sends to S a [SignedTransaction] that includes the state as input, B's cash as input, the state with the new + * owner key as output, and any change cash as output. It contains a single signature from B but isn't valid because + * it lacks a signature from S authorising movement of the asset. + * 3. S signs it and commits it to the ledger, notarising it and distributing the final signed transaction back + * to B. + * + * Assuming no malicious termination, they both end the flow being in possession of a valid, signed transaction + * that represents an atomic asset swap. + * + * Note that it's the *seller* who initiates contact with the buyer, not vice-versa as you might imagine. + */ +object TwoPartyTradeFlow { + // TODO: Common elements in multi-party transaction consensus and signing should be refactored into a superclass of this + // and [AbstractStateReplacementFlow]. + + class UnacceptablePriceException(givenPrice: Amount) : FlowException("Unacceptable price: $givenPrice") + + class AssetMismatchException(val expectedTypeName: String, val typeName: String) : FlowException() { + override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName" + } + + /** + * This object is serialised to the network and is the first flow message the seller sends to the buyer. + * + * @param payToIdentity anonymous identity of the seller, for payment to be sent to. + */ + @CordaSerializable + data class SellerTradeInfo( + val price: Amount, + val payToIdentity: PartyAndCertificate + ) + + open class Seller(private val otherSideSession: FlowSession, + private val assetToSell: StateAndRef, + private val price: Amount, + private val myParty: PartyAndCertificate, // TODO Left because in tests it's used to pass anonymous party. + override val progressTracker: ProgressTracker = Seller.tracker()) : FlowLogic() { + + companion object { + object AWAITING_PROPOSAL : ProgressTracker.Step("Awaiting transaction proposal") + // DOCSTART 3 + object VERIFYING_AND_SIGNING : ProgressTracker.Step("Verifying and signing transaction proposal") { + override fun childProgressTracker() = SignTransactionFlow.tracker() + } + // DOCEND 3 + + fun tracker() = ProgressTracker(AWAITING_PROPOSAL, VERIFYING_AND_SIGNING) + } + + // DOCSTART 4 + @Suspendable + override fun call(): SignedTransaction { + progressTracker.currentStep = AWAITING_PROPOSAL + // Make the first message we'll send to kick off the flow. + val hello = SellerTradeInfo(price, myParty) + // What we get back from the other side is a transaction that *might* be valid and acceptable to us, + // but we must check it out thoroughly before we sign! + // SendTransactionFlow allows seller to access our data to resolve the transaction. + subFlow(SendStateAndRefFlow(otherSideSession, listOf(assetToSell))) + otherSideSession.send(hello) + + // Verify and sign the transaction. + progressTracker.currentStep = VERIFYING_AND_SIGNING + + // Sync identities to ensure we know all of the identities involved in the transaction we're about to + // be asked to sign + subFlow(IdentitySyncFlow.Receive(otherSideSession)) + + // DOCSTART 5 + val signTransactionFlow = object : SignTransactionFlow(otherSideSession, VERIFYING_AND_SIGNING.childProgressTracker()) { + override fun checkTransaction(stx: SignedTransaction) { + // Verify that we know who all the participants in the transaction are + val states: Iterable = stx.tx.inputs.map { serviceHub.loadState(it).data } + stx.tx.outputs.map { it.data } + states.forEach { state -> + state.participants.forEach { anon -> + require(serviceHub.identityService.wellKnownPartyFromAnonymous(anon) != null) { + "Transaction state $state involves unknown participant $anon" + } + } + } + + if (stx.tx.outputStates.sumCashBy(myParty.party).withoutIssuer() != price) + throw FlowException("Transaction is not sending us the right amount of cash") + } + } + + val txId = subFlow(signTransactionFlow).id + // DOCEND 5 + + return waitForLedgerCommit(txId) + } + // DOCEND 4 + + // Following comment moved here so that it doesn't appear in the docsite: + // There are all sorts of funny games a malicious secondary might play with it sends maybeSTX, + // we should fix them: + // + // - This tx may attempt to send some assets we aren't intending to sell to the secondary, if + // we're reusing keys! So don't reuse keys! + // - This tx may include output states that impose odd conditions on the movement of the cash, + // once we implement state pairing. + // + // but the goal of this code is not to be fully secure (yet), but rather, just to find good ways to + // express flow state machines on top of the messaging layer. + } + + open class Buyer(private val sellerSession: FlowSession, + private val notary: Party, + private val acceptablePrice: Amount, + private val typeToBuy: Class, + private val anonymous: Boolean) : FlowLogic() { + constructor(otherSideSession: FlowSession, notary: Party, acceptablePrice: Amount, typeToBuy: Class) : + this(otherSideSession, notary, acceptablePrice, typeToBuy, true) + // DOCSTART 2 + object RECEIVING : ProgressTracker.Step("Waiting for seller trading info") + + object VERIFYING : ProgressTracker.Step("Verifying seller assets") + object SIGNING : ProgressTracker.Step("Generating and signing transaction proposal") + object COLLECTING_SIGNATURES : ProgressTracker.Step("Collecting signatures from other parties") { + override fun childProgressTracker() = CollectSignaturesFlow.tracker() + } + + object RECORDING : ProgressTracker.Step("Recording completed transaction") { + // TODO: Currently triggers a race condition on Team City. See https://github.com/corda/corda/issues/733. + // override fun childProgressTracker() = FinalityFlow.tracker() + } + + override val progressTracker = ProgressTracker(RECEIVING, VERIFYING, SIGNING, COLLECTING_SIGNATURES, RECORDING) + // DOCEND 2 + + // DOCSTART 1 + @Suspendable + override fun call(): SignedTransaction { + // Wait for a trade request to come in from the other party. + progressTracker.currentStep = RECEIVING + val (assetForSale, tradeRequest) = receiveAndValidateTradeRequest() + + // Create the identity we'll be paying to, and send the counterparty proof we own the identity + val buyerAnonymousIdentity = if (anonymous) + serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, false) + else + ourIdentityAndCert + // Put together a proposed transaction that performs the trade, and sign it. + progressTracker.currentStep = SIGNING + val (ptx, cashSigningPubKeys) = assembleSharedTX(assetForSale, tradeRequest, buyerAnonymousIdentity) + + // Now sign the transaction with whatever keys we need to move the cash. + val partSignedTx = serviceHub.signInitialTransaction(ptx, cashSigningPubKeys) + + // Sync up confidential identities in the transaction with our counterparty + 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. + progressTracker.currentStep = COLLECTING_SIGNATURES + val sellerSignature = subFlow(CollectSignatureFlow(partSignedTx, sellerSession, sellerSession.counterparty.owningKey)) + val twiceSignedTx = partSignedTx + sellerSignature + + // Notarise and record the transaction. + progressTracker.currentStep = RECORDING + return subFlow(FinalityFlow(twiceSignedTx)) + } + + @Suspendable + private fun receiveAndValidateTradeRequest(): Pair, SellerTradeInfo> { + val assetForSale = subFlow(ReceiveStateAndRefFlow(sellerSession)).single() + return assetForSale to sellerSession.receive().unwrap { + progressTracker.currentStep = VERIFYING + // What is the seller trying to sell us? + val asset = assetForSale.state.data + val assetTypeName = asset.javaClass.name + + // The asset must either be owned by the well known identity of the counterparty, or we must be able to + // prove the owner is a confidential identity of the counterparty. + val assetForSaleIdentity = serviceHub.identityService.wellKnownPartyFromAnonymous(asset.owner) + require(assetForSaleIdentity == sellerSession.counterparty) + + // Register the identity we're about to send payment to. This shouldn't be the same as the asset owner + // identity, so that anonymity is enforced. + val wellKnownPayToIdentity = serviceHub.identityService.verifyAndRegisterIdentity(it.payToIdentity) + require(wellKnownPayToIdentity?.party == sellerSession.counterparty) { "Well known identity to pay to must match counterparty identity" } + + if (it.price > acceptablePrice) + throw UnacceptablePriceException(it.price) + if (!typeToBuy.isInstance(asset)) + throw AssetMismatchException(typeToBuy.name, assetTypeName) + + it + } + } + + @Suspendable + private fun assembleSharedTX(assetForSale: StateAndRef, tradeRequest: SellerTradeInfo, buyerAnonymousIdentity: PartyAndCertificate): SharedTx { + val ptx = TransactionBuilder(notary) + + // Add input and output states for the movement of cash, by using the Cash contract to generate the states + val (tx, cashSigningPubKeys) = PtCash.generateSpend(serviceHub, ptx, tradeRequest.price, ourIdentityAndCert, tradeRequest.payToIdentity.party) + + // Add inputs/outputs/a command for the movement of the asset. + tx.addInputState(assetForSale) + + val (command, state) = assetForSale.state.data.withNewOwner(buyerAnonymousIdentity.party) + tx.addOutputState(state, assetForSale.state.contract, assetForSale.state.notary) + tx.addCommand(command, assetForSale.state.data.owner.owningKey) + + // We set the transaction's time-window: it may be that none of the contracts need this! + // But it can't hurt to have one. + val currentTime = serviceHub.clock.instant() + tx.setTimeWindow(currentTime, 30.seconds) + + return SharedTx(tx, cashSigningPubKeys) + } + // DOCEND 1 + + data class SharedTx(val tx: TransactionBuilder, val cashSigningPubKeys: List) + } +} diff --git a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/PtCommercialPaperTests.kt b/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/PtCommercialPaperTests.kt index d6b11c698b..c1104548f5 100644 --- a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/PtCommercialPaperTests.kt +++ b/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/PtCommercialPaperTests.kt @@ -9,8 +9,8 @@ import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.days import net.corda.core.utilities.seconds -import net.corda.finance.DOLLARS -import net.corda.finance.`issued by` +import net.corda.ptflows.DOLLARS +import net.corda.ptflows.`issued by` import net.corda.ptflows.contracts.asset.* import net.corda.testing.* import net.corda.ptflows.contracts.asset.fillWithSomeTestCash diff --git a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/TwoPartyTradeFlowTest.kt b/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/TwoPartyTradeFlowTest.kt new file mode 100644 index 0000000000..81f7299dfe --- /dev/null +++ b/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/TwoPartyTradeFlowTest.kt @@ -0,0 +1,779 @@ +package net.corda.ptflows.contracts.flows + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.concurrent.CordaFuture +import net.corda.core.contracts.* +import net.corda.core.crypto.* +import net.corda.core.flows.* +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.FlowStateMachine +import net.corda.core.internal.concurrent.map +import net.corda.core.internal.rootCause +import net.corda.core.messaging.DataFeed +import net.corda.core.messaging.SingleMessageRecipient +import net.corda.core.messaging.StateMachineTransactionMapping +import net.corda.core.node.services.Vault +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.core.toFuture +import net.corda.core.transactions.SignedTransaction +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.transactions.WireTransaction +import net.corda.core.utilities.days +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.toNonEmptySet +import net.corda.core.utilities.unwrap +import net.corda.ptflows.DOLLARS +import net.corda.ptflows.`issued by` +import net.corda.ptflows.contracts.PtCommercialPaper +import net.corda.ptflows.contracts.asset.CASH +import net.corda.ptflows.contracts.asset.PtCash +import net.corda.ptflows.contracts.asset.`issued by` +import net.corda.ptflows.contracts.asset.`owned by` +import net.corda.ptflows.flows.TwoPartyTradeFlow.Buyer +import net.corda.ptflows.flows.TwoPartyTradeFlow.Seller +import net.corda.node.internal.StartedNode +import net.corda.node.services.api.WritableTransactionStorage +import net.corda.node.services.config.NodeConfiguration +import net.corda.node.services.persistence.DBTransactionStorage +import net.corda.node.utilities.CordaPersistence +import net.corda.nodeapi.internal.ServiceInfo +import net.corda.testing.* +import net.corda.ptflows.contracts.asset.fillWithSomeTestCash +import net.corda.testing.node.InMemoryMessagingNetwork +import net.corda.testing.node.MockNetwork +import net.corda.testing.node.pumpReceive +import org.assertj.core.api.Assertions.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import rx.Observable +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.math.BigInteger +import java.security.KeyPair +import java.util.* +import java.util.jar.JarOutputStream +import java.util.zip.ZipEntry +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +/** + * In this example, Alice wishes to sell her commercial paper to Bob in return for $1,000,000 and they wish to do + * it on the ledger atomically. Therefore they must work together to build a transaction. + * + * We assume that Alice and Bob already found each other via some market, and have agreed the details already. + */ +class TwoPartyTradeFlowTests { + private lateinit var mockNet: MockNetwork + + @Before + fun before() { + setCordappPackages("net.corda.ptflows.contracts") + LogHelper.setLevel("platform.trade", "core.contract.TransactionGroup", "recordingmap") + } + + @After + fun after() { + mockNet.stopNodes() + LogHelper.reset("platform.trade", "core.contract.TransactionGroup", "recordingmap") + unsetCordappPackages() + } + + @Test + fun `trade cash for commercial paper`() { + // We run this in parallel threads to help catch any race conditions that may exist. The other tests + // we run in the unit test thread exclusively to speed things up, ensure deterministic results and + // allow interruption half way through. + mockNet = MockNetwork(false, true) + + ledger(initialiseSerialization = false) { + val basketOfNodes = mockNet.createSomeNodes(3) + val notaryNode = basketOfNodes.notaryNode + val aliceNode = basketOfNodes.partyNodes[0] + val bobNode = basketOfNodes.partyNodes[1] + val bankNode = basketOfNodes.partyNodes[2] + val cashIssuer = bankNode.info.chooseIdentity().ref(1) + val cpIssuer = bankNode.info.chooseIdentity().ref(1, 2, 3) + val notary = aliceNode.services.getDefaultNotary() + + + aliceNode.internals.disableDBCloseOnStop() + bobNode.internals.disableDBCloseOnStop() + + bobNode.database.transaction { + bobNode.services.fillWithSomeTestCash(2000.DOLLARS, bankNode.services, outputNotary = notary, + issuedBy = cashIssuer) + } + + val alicesFakePaper = aliceNode.database.transaction { + fillUpForSeller(false, cpIssuer, aliceNode.info.chooseIdentity(), + 1200.DOLLARS `issued by` bankNode.info.chooseIdentity().ref(0), null, notary).second + } + + insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) + + val (bobStateMachine, aliceResult) = runBuyerAndSeller(notary, aliceNode, bobNode, + "alice's paper".outputStateAndRef()) + + // TODO: Verify that the result was inserted into the transaction database. + // assertEquals(bobResult.get(), aliceNode.storage.validatedTransactions[aliceResult.get().id]) + assertEquals(aliceResult.getOrThrow(), bobStateMachine.getOrThrow().resultFuture.getOrThrow()) + + aliceNode.dispose() + bobNode.dispose() + +// aliceNode.database.transaction { +// assertThat(aliceNode.checkpointStorage.checkpoints()).isEmpty() +// } + aliceNode.internals.manuallyCloseDB() +// bobNode.database.transaction { +// assertThat(bobNode.checkpointStorage.checkpoints()).isEmpty() +// } + bobNode.internals.manuallyCloseDB() + } + } + + @Test(expected = InsufficientBalanceException::class) + fun `trade cash for commercial paper fails using soft locking`() { + mockNet = MockNetwork(false, true) + + ledger(initialiseSerialization = false) { + val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) + val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name) + val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name) + val bankNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOC.name) + val issuer = bankNode.info.chooseIdentity().ref(1) + val notary = aliceNode.services.getDefaultNotary() + + aliceNode.internals.disableDBCloseOnStop() + bobNode.internals.disableDBCloseOnStop() + + val cashStates = bobNode.database.transaction { + bobNode.services.fillWithSomeTestCash(2000.DOLLARS, bankNode.services, notary, 3, 3, + issuedBy = issuer) + } + + val alicesFakePaper = aliceNode.database.transaction { + fillUpForSeller(false, issuer, aliceNode.info.chooseIdentity(), + 1200.DOLLARS `issued by` bankNode.info.chooseIdentity().ref(0), null, notary).second + } + + insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) + + val cashLockId = UUID.randomUUID() + bobNode.database.transaction { + // lock the cash states with an arbitrary lockId (to prevent the Buyer flow from claiming the states) + val refs = cashStates.states.map { it.ref } + if (refs.isNotEmpty()) { + bobNode.services.vaultService.softLockReserve(cashLockId, refs.toNonEmptySet()) + } + } + + val (bobStateMachine, aliceResult) = runBuyerAndSeller(notary, aliceNode, bobNode, + "alice's paper".outputStateAndRef()) + + assertEquals(aliceResult.getOrThrow(), bobStateMachine.getOrThrow().resultFuture.getOrThrow()) + + aliceNode.dispose() + bobNode.dispose() + +// aliceNode.database.transaction { +// assertThat(aliceNode.checkpointStorage.checkpoints()).isEmpty() +// } + aliceNode.internals.manuallyCloseDB() +// bobNode.database.transaction { +// assertThat(bobNode.checkpointStorage.checkpoints()).isEmpty() +// } + bobNode.internals.manuallyCloseDB() + } + } + + @Test + fun `shutdown and restore`() { + mockNet = MockNetwork(false) + ledger(initialiseSerialization = false) { + val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) + val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name) + var bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name) + val bankNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOC.name) + val issuer = bankNode.info.chooseIdentity().ref(1, 2, 3) + + // Let the nodes know about each other - normally the network map would handle this + mockNet.registerIdentities() + + aliceNode.database.transaction { + aliceNode.services.identityService.verifyAndRegisterIdentity(bobNode.info.chooseIdentityAndCert()) + } + bobNode.database.transaction { + bobNode.services.identityService.verifyAndRegisterIdentity(aliceNode.info.chooseIdentityAndCert()) + } + aliceNode.internals.disableDBCloseOnStop() + bobNode.internals.disableDBCloseOnStop() + + val bobAddr = bobNode.network.myAddress as InMemoryMessagingNetwork.PeerHandle + val networkMapAddress = notaryNode.network.myAddress + + mockNet.runNetwork() // Clear network map registration messages + val notary = aliceNode.services.getDefaultNotary() + + bobNode.database.transaction { + bobNode.services.fillWithSomeTestCash(2000.DOLLARS, bankNode.services, outputNotary = notary, + issuedBy = issuer) + } + val alicesFakePaper = aliceNode.database.transaction { + fillUpForSeller(false, issuer, aliceNode.info.chooseIdentity(), + 1200.DOLLARS `issued by` bankNode.info.chooseIdentity().ref(0), null, notary).second + } + insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) + val aliceFuture = runBuyerAndSeller(notary, aliceNode, bobNode, "alice's paper".outputStateAndRef()).sellerResult + + // Everything is on this thread so we can now step through the flow one step at a time. + // Seller Alice already sent a message to Buyer Bob. Pump once: + bobNode.pumpReceive() + + // Bob sends a couple of queries for the dependencies back to Alice. Alice reponds. + aliceNode.pumpReceive() + bobNode.pumpReceive() + aliceNode.pumpReceive() + bobNode.pumpReceive() + aliceNode.pumpReceive() + bobNode.pumpReceive() + +// // OK, now Bob has sent the partial transaction back to Alice and is waiting for Alice's signature. +// bobNode.database.transaction { +// assertThat(bobNode.checkpointStorage.checkpoints()).hasSize(1) +// } + + val storage = bobNode.services.validatedTransactions + val bobTransactionsBeforeCrash = bobNode.database.transaction { + (storage as DBTransactionStorage).transactions + } + assertThat(bobTransactionsBeforeCrash).isNotEmpty + + // .. and let's imagine that Bob's computer has a power cut. He now has nothing now beyond what was on disk. + bobNode.dispose() + + // Alice doesn't know that and carries on: she wants to know about the cash transactions he's trying to use. + // She will wait around until Bob comes back. + assertThat(aliceNode.pumpReceive()).isNotNull() + + // FIXME: Knowledge of confidential identities is lost on node shutdown, so Bob's node now refuses to sign the + // transaction because it has no idea who the parties are. + + // ... bring the node back up ... the act of constructing the SMM will re-register the message handlers + // that Bob was waiting on before the reboot occurred. + bobNode = mockNet.createNode(networkMapAddress, bobAddr.id, object : MockNetwork.Factory { + override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, + advertisedServices: Set, id: Int, overrideServices: Map?, + entropyRoot: BigInteger): MockNetwork.MockNode { + return MockNetwork.MockNode(config, network, networkMapAddr, advertisedServices, bobAddr.id, overrideServices, entropyRoot) + } + }, BOB.name) + + // Find the future representing the result of this state machine again. + val bobFuture = bobNode.smm.findStateMachines(BuyerAcceptor::class.java).single().second + + // And off we go again. + mockNet.runNetwork() + + // Bob is now finished and has the same transaction as Alice. + assertThat(bobFuture.getOrThrow()).isEqualTo(aliceFuture.getOrThrow()) + + assertThat(bobNode.smm.findStateMachines(Buyer::class.java)).isEmpty() +// bobNode.database.transaction { +// assertThat(bobNode.checkpointStorage.checkpoints()).isEmpty() +// } +// aliceNode.database.transaction { +// assertThat(aliceNode.checkpointStorage.checkpoints()).isEmpty() +// } + + bobNode.database.transaction { + val restoredBobTransactions = bobTransactionsBeforeCrash.filter { + bobNode.services.validatedTransactions.getTransaction(it.id) != null + } + assertThat(restoredBobTransactions).containsAll(bobTransactionsBeforeCrash) + } + + aliceNode.internals.manuallyCloseDB() + bobNode.internals.manuallyCloseDB() + } + } + + // Creates a mock node with an overridden storage service that uses a RecordingMap, that lets us test the order + // of gets and puts. + private fun makeNodeWithTracking( + networkMapAddress: SingleMessageRecipient?, + name: CordaX500Name): StartedNode { + // Create a node in the mock network ... + return mockNet.createNode(networkMapAddress, nodeFactory = object : MockNetwork.Factory { + override fun create(config: NodeConfiguration, + network: MockNetwork, + networkMapAddr: SingleMessageRecipient?, + advertisedServices: Set, id: Int, + overrideServices: Map?, + entropyRoot: BigInteger): MockNetwork.MockNode { + return object : MockNetwork.MockNode(config, network, networkMapAddr, advertisedServices, id, overrideServices, entropyRoot) { + // That constructs a recording tx storage + override fun makeTransactionStorage(): WritableTransactionStorage { + return RecordingTransactionStorage(database, super.makeTransactionStorage()) + } + } + } + }, legalName = name) + } + + @Test + fun `check dependencies of sale asset are resolved`() { + mockNet = MockNetwork(false) + + val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) + val aliceNode = makeNodeWithTracking(notaryNode.network.myAddress, ALICE.name) + val bobNode = makeNodeWithTracking(notaryNode.network.myAddress, BOB.name) + val bankNode = makeNodeWithTracking(notaryNode.network.myAddress, BOC.name) + val issuer = bankNode.info.chooseIdentity().ref(1, 2, 3) + mockNet.runNetwork() + notaryNode.internals.ensureRegistered() + val notary = aliceNode.services.getDefaultNotary() + + mockNet.registerIdentities() + + ledger(aliceNode.services, initialiseSerialization = false) { + + // Insert a prospectus type attachment into the commercial paper transaction. + val stream = ByteArrayOutputStream() + JarOutputStream(stream).use { + it.putNextEntry(ZipEntry("Prospectus.txt")) + it.write("Our commercial paper is top notch stuff".toByteArray()) + it.closeEntry() + } + val attachmentID = aliceNode.database.transaction { + attachment(ByteArrayInputStream(stream.toByteArray())) + } + + 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(), + 1200.DOLLARS `issued by` bankNode.info.chooseIdentity().ref(0), attachmentID, notary).second + } + val alicesSignedTxns = insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) + + mockNet.runNetwork() // Clear network map registration messages + + runBuyerAndSeller(notary, aliceNode, bobNode, "alice's paper".outputStateAndRef()) + + mockNet.runNetwork() + + run { + val records = (bobNode.services.validatedTransactions as RecordingTransactionStorage).records + // Check Bobs's database accesses as Bob's cash transactions are downloaded by Alice. + records.expectEvents(isStrict = false) { + sequence( + // Buyer Bob is told about Alice's commercial paper, but doesn't know it .. + expect(TxRecord.Get(alicesFakePaper[0].id)), + // He asks and gets the tx, validates it, sees it's a self issue with no dependencies, stores. + expect(TxRecord.Add(alicesSignedTxns.values.first())), + // Alice gets Bob's proposed transaction and doesn't know his two cash states. She asks, Bob answers. + expect(TxRecord.Get(bobsFakeCash[1].id)), + expect(TxRecord.Get(bobsFakeCash[2].id)), + // Alice notices that Bob's cash txns depend on a third tx she also doesn't know. She asks, Bob answers. + expect(TxRecord.Get(bobsFakeCash[0].id)) + ) + } + + // Bob has downloaded the attachment. + bobNode.database.transaction { + bobNode.services.attachments.openAttachment(attachmentID)!!.openAsJAR().use { + it.nextJarEntry + val contents = it.reader().readText() + assertTrue(contents.contains("Our commercial paper is top notch stuff")) + } + } + } + + // And from Alice's perspective ... + run { + val records = (aliceNode.services.validatedTransactions as RecordingTransactionStorage).records + records.expectEvents(isStrict = false) { + sequence( + // Seller Alice sends her seller info to Bob, who wants to check the asset for sale. + // He requests, Alice looks up in her DB to send the tx to Bob + expect(TxRecord.Get(alicesFakePaper[0].id)), + // Seller Alice gets a proposed tx which depends on Bob's two cash txns and her own tx. + expect(TxRecord.Get(bobsFakeCash[1].id)), + expect(TxRecord.Get(bobsFakeCash[2].id)), + expect(TxRecord.Get(alicesFakePaper[0].id)), + // Alice notices that Bob's cash txns depend on a third tx she also doesn't know. + expect(TxRecord.Get(bobsFakeCash[0].id)), + // Bob answers with the transactions that are now all verifiable, as Alice bottomed out. + // Bob's transactions are valid, so she commits to the database + expect(TxRecord.Add(bobsSignedTxns[bobsFakeCash[0].id]!!)), + expect(TxRecord.Get(bobsFakeCash[0].id)), // Verify + expect(TxRecord.Add(bobsSignedTxns[bobsFakeCash[2].id]!!)), + expect(TxRecord.Get(bobsFakeCash[0].id)), // Verify + expect(TxRecord.Add(bobsSignedTxns[bobsFakeCash[1].id]!!)), + // Now she verifies the transaction is contract-valid (not signature valid) which means + // looking up the states again. + expect(TxRecord.Get(bobsFakeCash[1].id)), + expect(TxRecord.Get(bobsFakeCash[2].id)), + expect(TxRecord.Get(alicesFakePaper[0].id)), + // Alice needs to look up the input states to find out which Notary they point to + expect(TxRecord.Get(bobsFakeCash[1].id)), + expect(TxRecord.Get(bobsFakeCash[2].id)), + expect(TxRecord.Get(alicesFakePaper[0].id)) + ) + } + } + } + } + + @Test + fun `track works`() { + mockNet = MockNetwork(false) + + val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) + val aliceNode = makeNodeWithTracking(notaryNode.network.myAddress, ALICE.name) + val bobNode = makeNodeWithTracking(notaryNode.network.myAddress, BOB.name) + val bankNode = makeNodeWithTracking(notaryNode.network.myAddress, BOC.name) + val issuer = bankNode.info.chooseIdentity().ref(1, 2, 3) + + mockNet.runNetwork() + notaryNode.internals.ensureRegistered() + val notary = aliceNode.services.getDefaultNotary() + + mockNet.registerIdentities() + + ledger(aliceNode.services, initialiseSerialization = false) { + // Insert a prospectus type attachment into the commercial paper transaction. + val stream = ByteArrayOutputStream() + JarOutputStream(stream).use { + it.putNextEntry(ZipEntry("Prospectus.txt")) + it.write("Our commercial paper is top notch stuff".toByteArray()) + it.closeEntry() + } + val attachmentID = aliceNode.database.transaction { + attachment(ByteArrayInputStream(stream.toByteArray())) + } + + val bobsKey = bobNode.services.keyManagementService.keys.single() + val bobsFakeCash = bobNode.database.transaction { + fillUpForBuyer(false, issuer, AnonymousParty(bobsKey), notary) + }.second + insertFakeTransactions(bobsFakeCash, bobNode, notaryNode, bankNode) + + val alicesFakePaper = aliceNode.database.transaction { + fillUpForSeller(false, issuer, aliceNode.info.chooseIdentity(), + 1200.DOLLARS `issued by` bankNode.info.chooseIdentity().ref(0), attachmentID, notary).second + } + + insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) + + mockNet.runNetwork() // Clear network map registration messages + + val aliceTxStream = aliceNode.services.validatedTransactions.track().updates + val aliceTxMappings = with(aliceNode) { + database.transaction { services.stateMachineRecordedTransactionMapping.track().updates } + } + val aliceSmId = runBuyerAndSeller(notary, aliceNode, bobNode, + "alice's paper".outputStateAndRef()).sellerId + + mockNet.runNetwork() + + // We need to declare this here, if we do it inside [expectEvents] kotlin throws an internal compiler error(!). + val aliceTxExpectations = sequence( + expect { tx: SignedTransaction -> + require(tx.id == bobsFakeCash[0].id) + }, + expect { tx: SignedTransaction -> + require(tx.id == bobsFakeCash[2].id) + }, + expect { tx: SignedTransaction -> + require(tx.id == bobsFakeCash[1].id) + } + ) + aliceTxStream.expectEvents { aliceTxExpectations } + val aliceMappingExpectations = sequence( + expect { (stateMachineRunId, transactionId) -> + require(stateMachineRunId == aliceSmId) + require(transactionId == bobsFakeCash[0].id) + }, + expect { (stateMachineRunId, transactionId) -> + require(stateMachineRunId == aliceSmId) + require(transactionId == bobsFakeCash[2].id) + }, + expect { (stateMachineRunId, transactionId) -> + require(stateMachineRunId == aliceSmId) + require(transactionId == bobsFakeCash[1].id) + } + ) + aliceTxMappings.expectEvents { aliceMappingExpectations } + } + } + + @Test + fun `dependency with error on buyer side`() { + mockNet = MockNetwork(false) + ledger(initialiseSerialization = false) { + runWithError(true, false, "at least one cash input") + } + } + + @Test + fun `dependency with error on seller side`() { + mockNet = MockNetwork(false) + ledger(initialiseSerialization = false) { + runWithError(false, true, "Issuances have a time-window") + } + } + + private data class RunResult( + // The buyer is not created immediately, only when the seller starts running + val buyer: CordaFuture>, + val sellerResult: CordaFuture, + val sellerId: StateMachineRunId + ) + + private fun runBuyerAndSeller(notary: Party, + sellerNode: StartedNode, + buyerNode: StartedNode, + assetToSell: StateAndRef, + anonymous: Boolean = true): RunResult { + val buyerFlows: Observable> = buyerNode.internals.registerInitiatedFlow(BuyerAcceptor::class.java) + val firstBuyerFiber = buyerFlows.toFuture().map { it.stateMachine } + val seller = SellerInitiator(buyerNode.info.chooseIdentity(), notary, assetToSell, 1000.DOLLARS, anonymous) + val sellerResult = sellerNode.services.startFlow(seller).resultFuture + return RunResult(firstBuyerFiber, sellerResult, seller.stateMachine.id) + } + + @InitiatingFlow + class SellerInitiator(private val buyer: Party, + private val notary: Party, + private val assetToSell: StateAndRef, + private val price: Amount, + private val anonymous: Boolean) : FlowLogic() { + @Suspendable + override fun call(): SignedTransaction { + val myPartyAndCert = if (anonymous) { + serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, false) + } else { + ourIdentityAndCert + } + val buyerSession = initiateFlow(buyer) + buyerSession.send(TestTx(notary, price, anonymous)) + return subFlow(Seller( + buyerSession, + assetToSell, + price, + myPartyAndCert)) + } + } + + @InitiatedBy(SellerInitiator::class) + class BuyerAcceptor(private val sellerSession: FlowSession) : FlowLogic() { + @Suspendable + override fun call(): SignedTransaction { + val (notary, price, anonymous) = sellerSession.receive().unwrap { + require(serviceHub.networkMapCache.isNotary(it.notaryIdentity)) { "${it.notaryIdentity} is not a notary" } + it + } + return subFlow(Buyer(sellerSession, notary, price, PtCommercialPaper.State::class.java, anonymous)) + } + } + + @CordaSerializable + data class TestTx(val notaryIdentity: Party, val price: Amount, val anonymous: Boolean) + + private fun LedgerDSL.runWithError( + bobError: Boolean, + aliceError: Boolean, + expectedMessageSubstring: String + ) { + val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) + val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name) + val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name) + val bankNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOC.name) + val issuer = bankNode.info.chooseIdentity().ref(1, 2, 3) + + mockNet.runNetwork() + notaryNode.internals.ensureRegistered() + val notary = aliceNode.services.getDefaultNotary() + + // Let the nodes know about each other - normally the network map would handle this + mockNet.registerIdentities() + + val bobsBadCash = bobNode.database.transaction { + fillUpForBuyer(bobError, issuer, bobNode.info.chooseIdentity(), + notary).second + } + val alicesFakePaper = aliceNode.database.transaction { + fillUpForSeller(aliceError, issuer, aliceNode.info.chooseIdentity(), + 1200.DOLLARS `issued by` issuer, null, notary).second + } + + insertFakeTransactions(bobsBadCash, bobNode, notaryNode, bankNode) + insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) + + mockNet.runNetwork() // Clear network map registration messages + + val (bobStateMachine, aliceResult) = runBuyerAndSeller(notary, aliceNode, bobNode, "alice's paper".outputStateAndRef()) + + mockNet.runNetwork() + + val e = assertFailsWith { + if (bobError) + aliceResult.getOrThrow() + else + bobStateMachine.getOrThrow().resultFuture.getOrThrow() + } + val underlyingMessage = e.rootCause.message!! + if (expectedMessageSubstring !in underlyingMessage) { + assertEquals(expectedMessageSubstring, underlyingMessage) + } + } + + + private fun insertFakeTransactions( + wtxToSign: List, + node: StartedNode<*>, + notaryNode: StartedNode<*>, + vararg extraSigningNodes: StartedNode<*>): Map { + + val signed = wtxToSign.map { + val id = it.id + val sigs = mutableListOf() + val nodeKey = node.info.chooseIdentity().owningKey + sigs.add(node.services.keyManagementService.sign(SignableData(id, SignatureMetadata(1, Crypto.findSignatureScheme(nodeKey).schemeNumberID)), nodeKey)) + sigs.add(notaryNode.services.keyManagementService.sign(SignableData(id, SignatureMetadata(1, + Crypto.findSignatureScheme(notaryNode.info.legalIdentities[1].owningKey).schemeNumberID)), notaryNode.info.legalIdentities[1].owningKey)) + extraSigningNodes.forEach { currentNode -> + sigs.add(currentNode.services.keyManagementService.sign( + SignableData(id, SignatureMetadata(1, Crypto.findSignatureScheme(currentNode.info.chooseIdentity().owningKey).schemeNumberID)), + currentNode.info.chooseIdentity().owningKey) + ) + } + SignedTransaction(it, sigs) + } + return node.database.transaction { + node.services.recordTransactions(signed) + val validatedTransactions = node.services.validatedTransactions + if (validatedTransactions is RecordingTransactionStorage) { + validatedTransactions.records.clear() + } + signed.associateBy { it.id } + } + } + + private fun LedgerDSL.fillUpForBuyer( + withError: Boolean, + issuer: PartyAndReference, + owner: AbstractParty, + notary: Party): Pair, List> { + val interimOwner = issuer.party + // Bob (Buyer) has some cash he got from the Bank of Elbonia, Alice (Seller) has some commercial paper she + // wants to sell to Bob. + val eb1 = transaction(transactionBuilder = TransactionBuilder(notary = notary)) { + // Issued money to itself. + output(PtCash.PROGRAM_ID, "elbonian money 1", notary = notary) { 800.DOLLARS.CASH `issued by` issuer `owned by` interimOwner } + output(PtCash.PROGRAM_ID, "elbonian money 2", notary = notary) { 1000.DOLLARS.CASH `issued by` issuer `owned by` interimOwner } + if (!withError) { + command(issuer.party.owningKey) { PtCash.Commands.Issue() } + } else { + // Put a broken command on so at least a signature is created + command(issuer.party.owningKey) { PtCash.Commands.Move() } + } + timeWindow(TEST_TX_TIME) + if (withError) { + this.fails() + } else { + this.verifies() + } + } + + // Bob gets some cash onto the ledger from BoE + val bc1 = transaction(transactionBuilder = TransactionBuilder(notary = notary)) { + input("elbonian money 1") + output(PtCash.PROGRAM_ID, "bob cash 1", notary = notary) { 800.DOLLARS.CASH `issued by` issuer `owned by` owner } + command(interimOwner.owningKey) { PtCash.Commands.Move() } + this.verifies() + } + + val bc2 = transaction(transactionBuilder = TransactionBuilder(notary = notary)) { + input("elbonian money 2") + output(PtCash.PROGRAM_ID, "bob cash 2", notary = notary) { 300.DOLLARS.CASH `issued by` issuer `owned by` owner } + output(PtCash.PROGRAM_ID, notary = notary) { 700.DOLLARS.CASH `issued by` issuer `owned by` interimOwner } // Change output. + command(interimOwner.owningKey) { PtCash.Commands.Move() } + this.verifies() + } + + val vault = Vault(listOf("bob cash 1".outputStateAndRef(), "bob cash 2".outputStateAndRef())) + return Pair(vault, listOf(eb1, bc1, bc2)) + } + + private fun LedgerDSL.fillUpForSeller( + withError: Boolean, + issuer: PartyAndReference, + owner: AbstractParty, + amount: Amount>, + attachmentID: SecureHash?, + notary: Party): Pair, List> { + val ap = transaction(transactionBuilder = TransactionBuilder(notary = notary)) { + output(PtCommercialPaper.CP_PROGRAM_ID, "alice's paper", notary = notary) { + PtCommercialPaper.State(issuer, owner, amount, TEST_TX_TIME + 7.days) + } + command(issuer.party.owningKey) { PtCommercialPaper.Commands.Issue() } + if (!withError) + timeWindow(time = TEST_TX_TIME) + if (attachmentID != null) + attachment(attachmentID) + if (withError) { + this.fails() + } else { + this.verifies() + } + } + + val vault = Vault(listOf("alice's paper".outputStateAndRef())) + return Pair(vault, listOf(ap)) + } + + + class RecordingTransactionStorage(val database: CordaPersistence, val delegate: WritableTransactionStorage) : WritableTransactionStorage, SingletonSerializeAsToken() { + override fun track(): DataFeed, SignedTransaction> { + return database.transaction { + delegate.track() + } + } + + val records: MutableList = Collections.synchronizedList(ArrayList()) + override val updates: Observable + get() = delegate.updates + + override fun addTransaction(transaction: SignedTransaction): Boolean { + database.transaction { + records.add(TxRecord.Add(transaction)) + delegate.addTransaction(transaction) + } + return true + } + + override fun getTransaction(id: SecureHash): SignedTransaction? { + return database.transaction { + records.add(TxRecord.Get(id)) + delegate.getTransaction(id) + } + } + } + + interface TxRecord { + data class Add(val transaction: SignedTransaction) : TxRecord + data class Get(val id: SecureHash) : TxRecord + } + +} From 7ab94650a68f18a86dc8568e97ea59a735163a93 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Tue, 10 Oct 2017 17:00:36 +0100 Subject: [PATCH 05/15] Cash flows and unit tests --- .../corda/ptflows/flows/AbstractPtCashFlow.kt | 53 ++++++++ .../ptflows/flows/PtCashConfigDataFlow.kt | 40 ++++++ .../net/corda/ptflows/flows/PtCashExitFlow.kt | 82 ++++++++++++ .../flows/PtCashIssueAndPaymentFlow.kt | 49 +++++++ .../corda/ptflows/flows/PtCashIssueFlow.kt | 52 ++++++++ .../corda/ptflows/flows/PtCashPaymentFlow.kt | 74 +++++++++++ .../contracts/flows/CashExitFlowTests.kt | 79 ++++++++++++ .../contracts/flows/CashIssueFlowTests.kt | 69 ++++++++++ .../contracts/flows/CashPaymentFlowTests.kt | 121 ++++++++++++++++++ 9 files changed, 619 insertions(+) create mode 100644 perftestflows/src/main/kotlin/net/corda/ptflows/flows/AbstractPtCashFlow.kt create mode 100644 perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashConfigDataFlow.kt create mode 100644 perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashExitFlow.kt create mode 100644 perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashIssueAndPaymentFlow.kt create mode 100644 perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashIssueFlow.kt create mode 100644 perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashPaymentFlow.kt create mode 100644 perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashExitFlowTests.kt create mode 100644 perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashIssueFlowTests.kt create mode 100644 perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashPaymentFlowTests.kt diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/AbstractPtCashFlow.kt b/perftestflows/src/main/kotlin/net/corda/ptflows/flows/AbstractPtCashFlow.kt new file mode 100644 index 0000000000..31e256f721 --- /dev/null +++ b/perftestflows/src/main/kotlin/net/corda/ptflows/flows/AbstractPtCashFlow.kt @@ -0,0 +1,53 @@ +package net.corda.ptflows.flows + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.contracts.Amount +import net.corda.core.flows.FinalityFlow +import net.corda.core.flows.FlowException +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.NotaryException +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.Party +import net.corda.core.serialization.CordaSerializable +import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.ProgressTracker +import java.util.* + +/** + * Initiates a flow that produces an Issue/Move or Exit Cash transaction. + */ +abstract class AbstractPtCashFlow(override val progressTracker: ProgressTracker) : FlowLogic() { + companion object { + object GENERATING_ID : ProgressTracker.Step("Generating anonymous identities") + object GENERATING_TX : ProgressTracker.Step("Generating transaction") + object SIGNING_TX : ProgressTracker.Step("Signing transaction") + object FINALISING_TX : ProgressTracker.Step("Finalising transaction") + + fun tracker() = ProgressTracker(GENERATING_ID, GENERATING_TX, SIGNING_TX, FINALISING_TX) + } + + @Suspendable + protected fun finaliseTx(tx: SignedTransaction, extraParticipants: Set, message: String): SignedTransaction { + try { + return subFlow(FinalityFlow(tx, extraParticipants)) + } catch (e: NotaryException) { + throw PtCashException(message, e) + } + } + + /** + * Combined signed transaction and identity lookup map, which is the resulting data from regular cash flows. + * Specialised flows for unit tests differ from this. + * + * @param stx the signed transaction. + * @param recipient the identity used for the other side of the transaction, where applicable (i.e. this is + * null for exit transactions). For anonymous transactions this is the confidential identity generated for the + * transaction, otherwise this is the well known identity. + */ + @CordaSerializable + data class Result(val stx: SignedTransaction, val recipient: AbstractParty?) + + abstract class AbstractRequest(val amount: Amount) +} + +class PtCashException(message: String, cause: Throwable) : FlowException(message, cause) \ No newline at end of file diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashConfigDataFlow.kt b/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashConfigDataFlow.kt new file mode 100644 index 0000000000..a235ff6a51 --- /dev/null +++ b/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashConfigDataFlow.kt @@ -0,0 +1,40 @@ +package net.corda.ptflows.flows + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.flows.FlowException +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.StartableByRPC +import net.corda.core.serialization.CordaSerializable +import net.corda.ptflows.CHF +import net.corda.ptflows.EUR +import net.corda.ptflows.GBP +import net.corda.ptflows.USD +import java.util.* + +/** + * Flow to obtain cash cordapp app configuration. + */ +@StartableByRPC +class PtCashConfigDataFlow : FlowLogic() { + companion object { + private val supportedCurrencies = listOf(USD, GBP, CHF, EUR) + } + + @Suspendable + override fun call(): PtCashConfiguration { + val issuableCurrencies = supportedCurrencies.mapNotNull { + try { + // Currently it uses checkFlowPermission to determine the list of issuable currency as a temporary hack. + // TODO: get the config from proper configuration source. + checkFlowPermission("corda.issuer.$it", emptyMap()) + it + } catch (e: FlowException) { + null + } + } + return PtCashConfiguration(issuableCurrencies, supportedCurrencies) + } +} + +@CordaSerializable +data class PtCashConfiguration(val issuableCurrencies: List, val supportedCurrencies: List) diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashExitFlow.kt b/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashExitFlow.kt new file mode 100644 index 0000000000..2aa360137f --- /dev/null +++ b/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashExitFlow.kt @@ -0,0 +1,82 @@ +package net.corda.ptflows.flows + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.contracts.Amount +import net.corda.core.contracts.InsufficientBalanceException +import net.corda.core.flows.StartableByRPC +import net.corda.core.identity.Party +import net.corda.core.node.services.queryBy +import net.corda.core.node.services.vault.DEFAULT_PAGE_NUM +import net.corda.core.node.services.vault.PageSpecification +import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria +import net.corda.core.serialization.CordaSerializable +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.ProgressTracker +import net.corda.ptflows.contracts.asset.PtCash +import net.corda.ptflows.contracts.asset.PtCashSelection +import net.corda.ptflows.issuedBy +import java.util.* + +/** + * Initiates a flow that produces an cash exit transaction. + * + * @param amount the amount of a currency to remove from the ledger. + * @param issuerRef the reference on the issued currency. Added to the node's legal identity to determine the + * issuer. + */ +@StartableByRPC +class PtCashExitFlow(private val amount: Amount, + private val issuerRef: OpaqueBytes, + progressTracker: ProgressTracker) : AbstractPtCashFlow(progressTracker) { + constructor(amount: Amount, issueRef: OpaqueBytes) : this(amount, issueRef, tracker()) + constructor(request: ExitRequest) : this(request.amount, request.issueRef, tracker()) + + companion object { + fun tracker() = ProgressTracker(GENERATING_TX, SIGNING_TX, FINALISING_TX) + } + + /** + * @return the signed transaction, and a mapping of parties to new anonymous identities generated + * (for this flow this map is always empty). + */ + @Suspendable + @Throws(PtCashException::class) + override fun call(): AbstractPtCashFlow.Result { + progressTracker.currentStep = GENERATING_TX + val builder = TransactionBuilder(notary = null) + val issuer = ourIdentity.ref(issuerRef) + val exitStates = PtCashSelection + .getInstance { serviceHub.jdbcSession().metaData } + .unconsumedCashStatesForSpending(serviceHub, amount, setOf(issuer.party), builder.notary, builder.lockId, setOf(issuer.reference)) + val signers = try { + PtCash().generateExit( + builder, + amount.issuedBy(issuer), + exitStates) + } catch (e: InsufficientBalanceException) { + throw PtCashException("Exiting more cash than exists", e) + } + + // Work out who the owners of the burnt states were (specify page size so we don't silently drop any if > DEFAULT_PAGE_SIZE) + val inputStates = serviceHub.vaultQueryService.queryBy(VaultQueryCriteria(stateRefs = builder.inputStates()), + PageSpecification(pageNumber = DEFAULT_PAGE_NUM, pageSize = builder.inputStates().size)).states + + // TODO: Is it safe to drop participants we don't know how to contact? Does not knowing how to contact them + // count as a reason to fail? + val participants: Set = inputStates + .mapNotNull { serviceHub.identityService.wellKnownPartyFromAnonymous(it.state.data.owner) } + .toSet() + // Sign transaction + progressTracker.currentStep = SIGNING_TX + val tx = serviceHub.signInitialTransaction(builder, signers) + + // Commit the transaction + progressTracker.currentStep = FINALISING_TX + val notarised = finaliseTx(tx, participants, "Unable to notarise exit") + return Result(notarised, null) + } + + @CordaSerializable + class ExitRequest(amount: Amount, val issueRef: OpaqueBytes) : AbstractRequest(amount) +} diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashIssueAndPaymentFlow.kt b/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashIssueAndPaymentFlow.kt new file mode 100644 index 0000000000..74cca5b215 --- /dev/null +++ b/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashIssueAndPaymentFlow.kt @@ -0,0 +1,49 @@ +package net.corda.ptflows.flows + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.contracts.Amount +import net.corda.core.flows.StartableByRPC +import net.corda.core.identity.Party +import net.corda.core.serialization.CordaSerializable +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.ProgressTracker +import java.util.* + +/** + * Initiates a flow that self-issues cash (which should then be sent to recipient(s) using a payment transaction). + * + * We issue cash only to ourselves so that all KYC/AML checks on payments are enforced consistently, rather than risk + * checks for issuance and payments differing. Outside of test scenarios it would be extremely unusual to issue cash + * and immediately transfer it, so impact of this limitation is considered minimal. + * + * @param amount the amount of currency to issue. + * @param issuerBankPartyRef a reference to put on the issued currency. + * @param notary the notary to set on the output states. + */ +@StartableByRPC +class PtCashIssueAndPaymentFlow(val amount: Amount, + val issueRef: OpaqueBytes, + val recipient: Party, + val anonymous: Boolean, + val notary: Party, + progressTracker: ProgressTracker) : AbstractPtCashFlow(progressTracker) { + constructor(amount: Amount, + issueRef: OpaqueBytes, + recipient: Party, + anonymous: Boolean, + notary: Party) : this(amount, issueRef, recipient, anonymous, notary, tracker()) + constructor(request: IssueAndPaymentRequest) : this(request.amount, request.issueRef, request.recipient, request.anonymous, request.notary, tracker()) + + @Suspendable + override fun call(): Result { + subFlow(PtCashIssueFlow(amount, issueRef, notary)) + return subFlow(PtCashPaymentFlow(amount, recipient, anonymous)) + } + + @CordaSerializable + class IssueAndPaymentRequest(amount: Amount, + val issueRef: OpaqueBytes, + val recipient: Party, + val notary: Party, + val anonymous: Boolean) : AbstractRequest(amount) +} \ No newline at end of file diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashIssueFlow.kt b/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashIssueFlow.kt new file mode 100644 index 0000000000..e249f5090e --- /dev/null +++ b/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashIssueFlow.kt @@ -0,0 +1,52 @@ +package net.corda.ptflows.flows + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.contracts.Amount +import net.corda.core.flows.StartableByRPC +import net.corda.core.identity.Party +import net.corda.core.serialization.CordaSerializable +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.ProgressTracker +import net.corda.ptflows.contracts.asset.PtCash +import net.corda.ptflows.issuedBy +import java.util.* + +/** + * Initiates a flow that self-issues cash (which should then be sent to recipient(s) using a payment transaction). + * + * We issue cash only to ourselves so that all KYC/AML checks on payments are enforced consistently, rather than risk + * checks for issuance and payments differing. Outside of test scenarios it would be extremely unusual to issue cash + * and immediately transfer it, so impact of this limitation is considered minimal. + * + * @param amount the amount of currency to issue. + * @param issuerBankPartyRef a reference to put on the issued currency. + * @param notary the notary to set on the output states. + */ +@StartableByRPC +class PtCashIssueFlow(private val amount: Amount, + private val issuerBankPartyRef: OpaqueBytes, + private val notary: Party, + progressTracker: ProgressTracker) : AbstractPtCashFlow(progressTracker) { + constructor(amount: Amount, + issuerBankPartyRef: OpaqueBytes, + notary: Party) : this(amount, issuerBankPartyRef, notary, tracker()) + constructor(request: IssueRequest) : this(request.amount, request.issueRef, request.notary, tracker()) + + @Suspendable + override fun call(): AbstractPtCashFlow.Result { + progressTracker.currentStep = GENERATING_TX + val builder = TransactionBuilder(notary) + val issuer = ourIdentity.ref(issuerBankPartyRef) + val signers = PtCash().generateIssue(builder, amount.issuedBy(issuer), ourIdentity, notary) + progressTracker.currentStep = SIGNING_TX + val tx = serviceHub.signInitialTransaction(builder, signers) + progressTracker.currentStep = FINALISING_TX + // There is no one to send the tx to as we're the only participants + val notarised = finaliseTx(tx, emptySet(), "Unable to notarise issue") + return Result(notarised, ourIdentity) + } + + @CordaSerializable + class IssueRequest(amount: Amount, val issueRef: OpaqueBytes, val notary: Party) : AbstractRequest(amount) +} diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashPaymentFlow.kt b/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashPaymentFlow.kt new file mode 100644 index 0000000000..bb92fea5ce --- /dev/null +++ b/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashPaymentFlow.kt @@ -0,0 +1,74 @@ +package net.corda.ptflows.flows + +import co.paralleluniverse.fibers.Suspendable +import net.corda.confidential.SwapIdentitiesFlow +import net.corda.core.contracts.Amount +import net.corda.core.contracts.InsufficientBalanceException +import net.corda.core.flows.StartableByRPC +import net.corda.core.identity.AnonymousParty +import net.corda.core.identity.Party +import net.corda.core.serialization.CordaSerializable +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.ProgressTracker +import net.corda.ptflows.contracts.asset.PtCash +import java.util.* + +/** + * Initiates a flow that sends cash to a recipient. + * + * @param amount the amount of a currency to pay to the recipient. + * @param recipient the party to pay the currency to. + * @param issuerConstraint if specified, the payment will be made using only cash issued by the given parties. + * @param anonymous whether to anonymous the recipient party. Should be true for normal usage, but may be false + * for testing purposes. + */ +@StartableByRPC +open class PtCashPaymentFlow( + val amount: Amount, + val recipient: Party, + val anonymous: Boolean, + progressTracker: ProgressTracker, + val issuerConstraint: Set = emptySet()) : AbstractPtCashFlow(progressTracker) { + /** A straightforward constructor that constructs spends using cash states of any issuer. */ + constructor(amount: Amount, recipient: Party) : this(amount, recipient, true, tracker()) + /** A straightforward constructor that constructs spends using cash states of any issuer. */ + constructor(amount: Amount, recipient: Party, anonymous: Boolean) : this(amount, recipient, anonymous, tracker()) + constructor(request: PaymentRequest) : this(request.amount, request.recipient, request.anonymous, tracker(), request.issuerConstraint) + + @Suspendable + override fun call(): AbstractPtCashFlow.Result { + progressTracker.currentStep = GENERATING_ID + val txIdentities = if (anonymous) { + subFlow(SwapIdentitiesFlow(recipient)) + } else { + emptyMap() + } + val anonymousRecipient = txIdentities[recipient] ?: recipient + progressTracker.currentStep = GENERATING_TX + val builder = TransactionBuilder(notary = null) + // TODO: Have some way of restricting this to states the caller controls + val (spendTX, keysForSigning) = try { + PtCash.generateSpend(serviceHub, + builder, + amount, + ourIdentityAndCert, + anonymousRecipient, + issuerConstraint) + } catch (e: InsufficientBalanceException) { + throw PtCashException("Insufficient cash for spend: ${e.message}", e) + } + + progressTracker.currentStep = SIGNING_TX + val tx = serviceHub.signInitialTransaction(spendTX, keysForSigning) + + progressTracker.currentStep = FINALISING_TX + val notarised = finaliseTx(tx, setOf(recipient), "Unable to notarise spend") + return Result(notarised, anonymousRecipient) + } + + @CordaSerializable + class PaymentRequest(amount: Amount, + val recipient: Party, + val anonymous: Boolean, + val issuerConstraint: Set = emptySet()) : AbstractRequest(amount) +} diff --git a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashExitFlowTests.kt b/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashExitFlowTests.kt new file mode 100644 index 0000000000..69986b6f44 --- /dev/null +++ b/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashExitFlowTests.kt @@ -0,0 +1,79 @@ +package net.corda.ptflows.contracts.flows + +import net.corda.core.identity.Party +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.getOrThrow +import net.corda.ptflows.flows.PtCashException +import net.corda.ptflows.flows.PtCashExitFlow +import net.corda.ptflows.flows.PtCashIssueFlow +import net.corda.ptflows.DOLLARS +import net.corda.ptflows.`issued by` +import net.corda.ptflows.contracts.asset.PtCash +import net.corda.node.internal.StartedNode +import net.corda.testing.chooseIdentity +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 +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class CashExitFlowTests { + private lateinit var mockNet : MockNetwork + private val initialBalance = 2000.DOLLARS + private val ref = OpaqueBytes.of(0x01) + private lateinit var bankOfCordaNode: StartedNode + private lateinit var bankOfCorda: Party + private lateinit var notaryNode: StartedNode + private lateinit var notary: Party + + @Before + fun start() { + setCordappPackages("net.corda.ptflows.contracts.asset") + mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin()) + val nodes = mockNet.createSomeNodes(1) + notaryNode = nodes.notaryNode + bankOfCordaNode = nodes.partyNodes[0] + bankOfCorda = bankOfCordaNode.info.chooseIdentity() + + mockNet.runNetwork() + notary = bankOfCordaNode.services.getDefaultNotary() + val future = bankOfCordaNode.services.startFlow(PtCashIssueFlow(initialBalance, ref, notary)).resultFuture + mockNet.runNetwork() + future.getOrThrow() + } + + @After + fun cleanUp() { + mockNet.stopNodes() + unsetCordappPackages() + } + + @Test + fun `exit some cash`() { + val exitAmount = 500.DOLLARS + val future = bankOfCordaNode.services.startFlow(PtCashExitFlow(exitAmount, ref)).resultFuture + mockNet.runNetwork() + val exitTx = future.getOrThrow().stx.tx + val expected = (initialBalance - exitAmount).`issued by`(bankOfCorda.ref(ref)) + assertEquals(1, exitTx.inputs.size) + assertEquals(1, exitTx.outputs.size) + val output = exitTx.outputsOfType().single() + assertEquals(expected, output.amount) + } + + @Test + fun `exit zero cash`() { + val expected = 0.DOLLARS + val future = bankOfCordaNode.services.startFlow(PtCashExitFlow(expected, ref)).resultFuture + mockNet.runNetwork() + assertFailsWith { + future.getOrThrow() + } + } +} diff --git a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashIssueFlowTests.kt b/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashIssueFlowTests.kt new file mode 100644 index 0000000000..318b1cc53b --- /dev/null +++ b/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashIssueFlowTests.kt @@ -0,0 +1,69 @@ +package net.corda.ptflows.contracts.flows + +import net.corda.core.identity.Party +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.getOrThrow +import net.corda.ptflows.DOLLARS +import net.corda.ptflows.`issued by` +import net.corda.ptflows.contracts.asset.PtCash +import net.corda.node.internal.StartedNode +import net.corda.ptflows.flows.PtCashIssueFlow +import net.corda.testing.chooseIdentity +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 +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class CashIssueFlowTests { + private lateinit var mockNet : MockNetwork + private lateinit var bankOfCordaNode: StartedNode + private lateinit var bankOfCorda: Party + private lateinit var notaryNode: StartedNode + private lateinit var notary: Party + + @Before + fun start() { + setCordappPackages("net.corda.ptflows.contracts.asset") + mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin()) + val nodes = mockNet.createSomeNodes(1) + notaryNode = nodes.notaryNode + bankOfCordaNode = nodes.partyNodes[0] + bankOfCorda = bankOfCordaNode.info.chooseIdentity() + + mockNet.runNetwork() + notary = bankOfCordaNode.services.getDefaultNotary() + } + + @After + fun cleanUp() { + mockNet.stopNodes() + } + + @Test + fun `issue some cash`() { + val expected = 500.DOLLARS + val ref = OpaqueBytes.of(0x01) + val future = bankOfCordaNode.services.startFlow(PtCashIssueFlow(expected, ref, notary)).resultFuture + mockNet.runNetwork() + val issueTx = future.getOrThrow().stx + val output = issueTx.tx.outputsOfType().single() + assertEquals(expected.`issued by`(bankOfCorda.ref(ref)), output.amount) + } + + @Test + fun `issue zero cash`() { + val expected = 0.DOLLARS + val ref = OpaqueBytes.of(0x01) + val future = bankOfCordaNode.services.startFlow(PtCashIssueFlow(expected, ref, notary)).resultFuture + mockNet.runNetwork() + assertFailsWith { + future.getOrThrow() + } + } +} diff --git a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashPaymentFlowTests.kt b/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashPaymentFlowTests.kt new file mode 100644 index 0000000000..7a2ee8eaf0 --- /dev/null +++ b/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashPaymentFlowTests.kt @@ -0,0 +1,121 @@ +package net.corda.ptflows.contracts.flows + +import net.corda.core.identity.Party +import net.corda.core.node.services.Vault +import net.corda.core.node.services.trackBy +import net.corda.core.node.services.vault.QueryCriteria +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.getOrThrow +import net.corda.ptflows.DOLLARS +import net.corda.ptflows.`issued by` +import net.corda.ptflows.contracts.asset.PtCash +import net.corda.ptflows.flows.PtCashException +import net.corda.ptflows.flows.PtCashIssueFlow +import net.corda.ptflows.flows.PtCashPaymentFlow +import net.corda.node.internal.StartedNode +import net.corda.testing.chooseIdentity +import net.corda.testing.expect +import net.corda.testing.expectEvents +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 +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class CashPaymentFlowTests { + private lateinit var mockNet : MockNetwork + private val initialBalance = 2000.DOLLARS + private val ref = OpaqueBytes.of(0x01) + private lateinit var bankOfCordaNode: StartedNode + private lateinit var bankOfCorda: Party + private lateinit var notaryNode: StartedNode + private lateinit var notary: Party + + @Before + fun start() { + setCordappPackages("net.corda.ptflows.contracts.asset") + mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin()) + val nodes = mockNet.createSomeNodes(1) + notaryNode = nodes.notaryNode + bankOfCordaNode = nodes.partyNodes[0] + bankOfCorda = bankOfCordaNode.info.chooseIdentity() + notary = notaryNode.services.getDefaultNotary() + val future = bankOfCordaNode.services.startFlow(PtCashIssueFlow(initialBalance, ref, notary)).resultFuture + mockNet.runNetwork() + future.getOrThrow() + } + + @After + fun cleanUp() { + mockNet.stopNodes() + } + + @Test + fun `pay some cash`() { + val payTo = notaryNode.info.chooseIdentity() + val expectedPayment = 500.DOLLARS + val expectedChange = 1500.DOLLARS + + bankOfCordaNode.database.transaction { + // Register for vault updates + val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL) + val (_, vaultUpdatesBoc) = bankOfCordaNode.services.vaultQueryService.trackBy(criteria) + val (_, vaultUpdatesBankClient) = notaryNode.services.vaultQueryService.trackBy(criteria) + + val future = bankOfCordaNode.services.startFlow(PtCashPaymentFlow(expectedPayment, + payTo)).resultFuture + mockNet.runNetwork() + future.getOrThrow() + + // Check Bank of Corda vault updates - we take in some issued cash and split it into $500 to the notary + // and $1,500 back to us, so we expect to consume one state, produce one state for our own vault + vaultUpdatesBoc.expectEvents { + expect { update -> + require(update.consumed.size == 1) { "Expected 1 consumed states, actual: $update" } + require(update.produced.size == 1) { "Expected 1 produced states, actual: $update" } + val changeState = update.produced.single().state.data + assertEquals(expectedChange.`issued by`(bankOfCorda.ref(ref)), changeState.amount) + } + } + + // Check notary node vault updates + vaultUpdatesBankClient.expectEvents { + expect { (consumed, produced) -> + require(consumed.isEmpty()) { consumed.size } + require(produced.size == 1) { produced.size } + val paymentState = produced.single().state.data + assertEquals(expectedPayment.`issued by`(bankOfCorda.ref(ref)), paymentState.amount) + } + } + } + } + + @Test + fun `pay more than we have`() { + val payTo = notaryNode.info.chooseIdentity() + val expected = 4000.DOLLARS + val future = bankOfCordaNode.services.startFlow(PtCashPaymentFlow(expected, + payTo)).resultFuture + mockNet.runNetwork() + assertFailsWith { + future.getOrThrow() + } + } + + @Test + fun `pay zero cash`() { + val payTo = notaryNode.info.chooseIdentity() + val expected = 0.DOLLARS + val future = bankOfCordaNode.services.startFlow(PtCashPaymentFlow(expected, + payTo)).resultFuture + mockNet.runNetwork() + assertFailsWith { + future.getOrThrow() + } + } +} From 894e407057ebf5ac413135823041fb1f13ed43f8 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Tue, 17 Oct 2017 09:54:55 +0100 Subject: [PATCH 06/15] Updated modules in compiler.xml --- .idea/compiler.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 25abc91c93..eb97a54cc4 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -77,6 +77,8 @@ + + @@ -99,6 +101,9 @@ + + + From 1241c79d7c4c5b7228cc165520ca86d6538ec624 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Tue, 17 Oct 2017 11:20:18 +0100 Subject: [PATCH 07/15] Remove Java parts and unused resources --- .../contracts/IPtCommercialPaperState.java | 23 - .../contracts/JavaPtCommercialPaper.java | 267 ----- .../ptflows/contracts/PtCommercialPaper.kt | 8 +- .../corda/ptflows/schemas/PtCashSchemaV1.kt | 4 +- .../schemas/PtCommercialPaperSchemaV1.kt | 6 +- .../contracts/LondonHolidayCalendar.txt | 1 - .../contracts/NewYorkHolidayCalendar.txt | 1 - .../main/resources/finance/utils/cities.txt | 1002 ----------------- .../contracts/PtCommercialPaperTests.kt | 47 +- 9 files changed, 26 insertions(+), 1333 deletions(-) delete mode 100644 perftestflows/src/main/java/net/corda/ptflows/contracts/IPtCommercialPaperState.java delete mode 100644 perftestflows/src/main/java/net/corda/ptflows/contracts/JavaPtCommercialPaper.java delete mode 100644 perftestflows/src/main/resources/finance/contracts/LondonHolidayCalendar.txt delete mode 100644 perftestflows/src/main/resources/finance/contracts/NewYorkHolidayCalendar.txt delete mode 100644 perftestflows/src/main/resources/finance/utils/cities.txt diff --git a/perftestflows/src/main/java/net/corda/ptflows/contracts/IPtCommercialPaperState.java b/perftestflows/src/main/java/net/corda/ptflows/contracts/IPtCommercialPaperState.java deleted file mode 100644 index edf6362ba9..0000000000 --- a/perftestflows/src/main/java/net/corda/ptflows/contracts/IPtCommercialPaperState.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.corda.ptflows.contracts; - - -import net.corda.core.contracts.Amount; -import net.corda.core.contracts.ContractState; -import net.corda.core.contracts.Issued; -import net.corda.core.identity.AbstractParty; - -import java.time.Instant; -import java.util.Currency; - -/* This is an interface solely created to demonstrate that the same kotlin tests can be run against - * either a Java implementation of the CommercialPaper or a kotlin implementation. - * Normally one would not duplicate an implementation in different languages for obvious reasons, but it demonstrates that - * ultimately either language can be used against a common test framework (and therefore can be used for real). - */ -public interface IPtCommercialPaperState extends ContractState { - IPtCommercialPaperState withOwner(AbstractParty newOwner); - - IPtCommercialPaperState withFaceValue(Amount> newFaceValue); - - IPtCommercialPaperState withMaturityDate(Instant newMaturityDate); -} diff --git a/perftestflows/src/main/java/net/corda/ptflows/contracts/JavaPtCommercialPaper.java b/perftestflows/src/main/java/net/corda/ptflows/contracts/JavaPtCommercialPaper.java deleted file mode 100644 index ce9c2d263f..0000000000 --- a/perftestflows/src/main/java/net/corda/ptflows/contracts/JavaPtCommercialPaper.java +++ /dev/null @@ -1,267 +0,0 @@ -package net.corda.ptflows.contracts; - - -import co.paralleluniverse.fibers.Suspendable; -import kotlin.Unit; -import net.corda.core.contracts.*; -import net.corda.core.crypto.NullKeys.NullPublicKey; -import net.corda.core.identity.AbstractParty; -import net.corda.core.identity.AnonymousParty; -import net.corda.core.identity.Party; -import net.corda.core.identity.PartyAndCertificate; -import net.corda.core.node.ServiceHub; -import net.corda.core.transactions.LedgerTransaction; -import net.corda.core.transactions.TransactionBuilder; -import net.corda.ptflows.contracts.asset.PtCash; -import net.corda.ptflows.utils.StateSummingUtilitiesKt; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.time.Instant; -import java.util.Collections; -import java.util.Currency; -import java.util.Iterator; -import java.util.List; -import java.util.stream.Collectors; - -import static net.corda.core.contracts.ContractsDSL.requireSingleCommand; -import static net.corda.core.contracts.ContractsDSL.requireThat; - -/** - * This is a Java version of the CommercialPaper contract (chosen because it's simple). This demonstrates how the - * use of Kotlin for implementation of the framework does not impose the same language choice on contract developers. - */ -@SuppressWarnings("unused") -public class JavaPtCommercialPaper implements Contract { - static final String JCP_PROGRAM_ID = "net.corda.ptflows.contracts.JavaPtCommercialPaper"; - - @SuppressWarnings("unused") - public static class State implements OwnableState, IPtCommercialPaperState { - private PartyAndReference issuance; - private AbstractParty owner; - private Amount> faceValue; - private Instant maturityDate; - - public State() { - } // For serialization - - public State(PartyAndReference issuance, AbstractParty owner, Amount> faceValue, - Instant maturityDate) { - this.issuance = issuance; - this.owner = owner; - this.faceValue = faceValue; - this.maturityDate = maturityDate; - } - - public State copy() { - return new State(this.issuance, this.owner, this.faceValue, this.maturityDate); - } - - public IPtCommercialPaperState withOwner(AbstractParty newOwner) { - return new State(this.issuance, newOwner, this.faceValue, this.maturityDate); - } - - @NotNull - @Override - public CommandAndState withNewOwner(@NotNull AbstractParty newOwner) { - return new CommandAndState(new Commands.Move(), new State(this.issuance, newOwner, this.faceValue, this.maturityDate)); - } - - public IPtCommercialPaperState withFaceValue(Amount> newFaceValue) { - return new State(this.issuance, this.owner, newFaceValue, this.maturityDate); - } - - public IPtCommercialPaperState withMaturityDate(Instant newMaturityDate) { - return new State(this.issuance, this.owner, this.faceValue, newMaturityDate); - } - - public PartyAndReference getIssuance() { - return issuance; - } - - @NotNull - public AbstractParty getOwner() { - return owner; - } - - public Amount> getFaceValue() { - return faceValue; - } - - public Instant getMaturityDate() { - return maturityDate; - } - - @Override - public boolean equals(Object that) { - if (this == that) return true; - if (that == null || getClass() != that.getClass()) return false; - - State state = (State) that; - - if (issuance != null ? !issuance.equals(state.issuance) : state.issuance != null) return false; - if (owner != null ? !owner.equals(state.owner) : state.owner != null) return false; - if (faceValue != null ? !faceValue.equals(state.faceValue) : state.faceValue != null) return false; - if (maturityDate != null ? !maturityDate.equals(state.maturityDate) : state.maturityDate != null) - return false; - return true; - } - - @Override - public int hashCode() { - int result = issuance != null ? issuance.hashCode() : 0; - result = 31 * result + (owner != null ? owner.hashCode() : 0); - result = 31 * result + (faceValue != null ? faceValue.hashCode() : 0); - result = 31 * result + (maturityDate != null ? maturityDate.hashCode() : 0); - return result; - } - - State withoutOwner() { - return new State(issuance, new AnonymousParty(NullPublicKey.INSTANCE), faceValue, maturityDate); - } - - @NotNull - @Override - public List getParticipants() { - return Collections.singletonList(this.owner); - } - } - - public interface Commands extends CommandData { - class Move implements Commands { - @Override - public boolean equals(Object obj) { - return obj instanceof Move; - } - } - - class Redeem implements Commands { - @Override - public boolean equals(Object obj) { - return obj instanceof Redeem; - } - } - - class Issue implements Commands { - @Override - public boolean equals(Object obj) { - return obj instanceof Issue; - } - } - } - - @NotNull - private List> extractCommands(@NotNull LedgerTransaction tx) { - return tx.getCommands() - .stream() - .filter((CommandWithParties command) -> command.getValue() instanceof Commands) - .map((CommandWithParties command) -> new CommandWithParties<>(command.getSigners(), command.getSigningParties(), (Commands) command.getValue())) - .collect(Collectors.toList()); - } - - @Override - public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { - - // Group by everything except owner: any modification to the CP at all is considered changing it fundamentally. - final List> groups = tx.groupStates(State.class, State::withoutOwner); - - // There are two possible things that can be done with this CP. The first is trading it. The second is redeeming - // it for cash on or after the maturity date. - final List> commands = tx.getCommands().stream().filter( - it -> it.getValue() instanceof Commands - ).collect(Collectors.toList()); - final CommandWithParties command = onlyElementOf(commands); - final TimeWindow timeWindow = tx.getTimeWindow(); - - for (final LedgerTransaction.InOutGroup group : groups) { - final List inputs = group.getInputs(); - final List outputs = group.getOutputs(); - if (command.getValue() instanceof Commands.Move) { - final CommandWithParties cmd = requireSingleCommand(tx.getCommands(), Commands.Move.class); - // There should be only a single input due to aggregation above - final State input = onlyElementOf(inputs); - - if (!cmd.getSigners().contains(input.getOwner().getOwningKey())) - throw new IllegalStateException("Failed requirement: the transaction is signed by the owner of the CP"); - - // Check the output CP state is the same as the input state, ignoring the owner field. - if (outputs.size() != 1) { - throw new IllegalStateException("the state is propagated"); - } - } else if (command.getValue() instanceof Commands.Redeem) { - final CommandWithParties cmd = requireSingleCommand(tx.getCommands(), Commands.Redeem.class); - - // There should be only a single input due to aggregation above - final State input = onlyElementOf(inputs); - - if (!cmd.getSigners().contains(input.getOwner().getOwningKey())) - throw new IllegalStateException("Failed requirement: the transaction is signed by the owner of the CP"); - - final Instant time = null == timeWindow - ? null - : timeWindow.getUntilTime(); - final Amount> received = StateSummingUtilitiesKt.sumCashBy(tx.getOutputStates(), input.getOwner()); - - requireThat(require -> { - require.using("must be timestamped", timeWindow != null); - require.using("received amount equals the face value: " - + received + " vs " + input.getFaceValue(), received.equals(input.getFaceValue())); - require.using("the paper must have matured", time != null && !time.isBefore(input.getMaturityDate())); - require.using("the received amount equals the face value", input.getFaceValue().equals(received)); - require.using("the paper must be destroyed", outputs.isEmpty()); - return Unit.INSTANCE; - }); - } else if (command.getValue() instanceof Commands.Issue) { - final CommandWithParties cmd = requireSingleCommand(tx.getCommands(), Commands.Issue.class); - final State output = onlyElementOf(outputs); - final Instant time = null == timeWindow - ? null - : timeWindow.getUntilTime(); - - requireThat(require -> { - require.using("output values sum to more than the inputs", inputs.isEmpty()); - require.using("output values sum to more than the inputs", output.faceValue.getQuantity() > 0); - require.using("must be timestamped", timeWindow != null); - require.using("the maturity date is not in the past", time != null && time.isBefore(output.getMaturityDate())); - require.using("output states are issued by a command signer", cmd.getSigners().contains(output.issuance.getParty().getOwningKey())); - return Unit.INSTANCE; - }); - } - } - } - - public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount> faceValue, @Nullable Instant maturityDate, @NotNull Party notary, Integer encumbrance) { - State state = new State(issuance, issuance.getParty(), faceValue, maturityDate); - TransactionState output = new TransactionState<>(state, JCP_PROGRAM_ID, notary, encumbrance); - return new TransactionBuilder(notary).withItems(output, new Command<>(new Commands.Issue(), issuance.getParty().getOwningKey())); - } - - public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount> faceValue, @Nullable Instant maturityDate, @NotNull Party notary) { - return generateIssue(issuance, faceValue, maturityDate, notary, null); - } - - @Suspendable - public void generateRedeem(final TransactionBuilder tx, - final StateAndRef paper, - final ServiceHub services, - final PartyAndCertificate ourIdentity) throws InsufficientBalanceException { - PtCash.generateSpend(services, tx, Structures.withoutIssuer(paper.getState().getData().getFaceValue()), ourIdentity, paper.getState().getData().getOwner(), Collections.emptySet()); - tx.addInputState(paper); - tx.addCommand(new Command<>(new Commands.Redeem(), paper.getState().getData().getOwner().getOwningKey())); - } - - public void generateMove(TransactionBuilder tx, StateAndRef paper, AbstractParty newOwner) { - tx.addInputState(paper); - tx.addOutputState(new TransactionState<>(new State(paper.getState().getData().getIssuance(), newOwner, paper.getState().getData().getFaceValue(), paper.getState().getData().getMaturityDate()), JCP_PROGRAM_ID, paper.getState().getNotary(), paper.getState().getEncumbrance())); - tx.addCommand(new Command<>(new Commands.Move(), paper.getState().getData().getOwner().getOwningKey())); - } - - private static T onlyElementOf(Iterable iterable) { - Iterator iter = iterable.iterator(); - T item = iter.next(); - if (iter.hasNext()) { - throw new IllegalArgumentException("Iterable has more than one element!"); - } - return item; - } -} diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/PtCommercialPaper.kt b/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/PtCommercialPaper.kt index 7c4825ecc1..cbc5693735 100644 --- a/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/PtCommercialPaper.kt +++ b/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/PtCommercialPaper.kt @@ -55,7 +55,7 @@ class PtCommercialPaper : Contract { override val owner: AbstractParty, val faceValue: Amount>, val maturityDate: Instant - ) : OwnableState, QueryableState, IPtCommercialPaperState{ + ) : OwnableState, QueryableState{ override val participants = listOf(owner) override fun withNewOwner(newOwner: AbstractParty) = CommandAndState(Commands.Move(), copy(owner = newOwner)) @@ -63,10 +63,10 @@ class PtCommercialPaper : Contract { override fun toString() = "${Emoji.newspaper}CommercialPaper(of $faceValue redeemable on $maturityDate by '$issuance', owned by $owner)" // Although kotlin is smart enough not to need these, as we are using the ICommercialPaperState, we need to declare them explicitly for use later, - override fun withOwner(newOwner: AbstractParty): IPtCommercialPaperState = copy(owner = newOwner) + fun withOwner(newOwner: AbstractParty): State = copy(owner = newOwner) - override fun withFaceValue(newFaceValue: Amount>): IPtCommercialPaperState = copy(faceValue = newFaceValue) - override fun withMaturityDate(newMaturityDate: Instant): IPtCommercialPaperState = copy(maturityDate = newMaturityDate) + fun withFaceValue(newFaceValue: Amount>): State = copy(faceValue = newFaceValue) + fun withMaturityDate(newMaturityDate: Instant): State = copy(maturityDate = newMaturityDate) /** Object Relational Mapping support. */ override fun supportedSchemas(): Iterable = listOf(PtCommercialPaperSchemaV1) diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCashSchemaV1.kt b/perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCashSchemaV1.kt index bf4f46c805..ca43d2f748 100644 --- a/perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCashSchemaV1.kt +++ b/perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCashSchemaV1.kt @@ -23,8 +23,8 @@ object PtCashSchema object PtCashSchemaV1 : MappedSchema(schemaFamily = PtCashSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCashState::class.java)) { @Entity @Table(name = "contract_cash_states", - indexes = arrayOf(Index(name = "ccy_code_idx", columnList = "ccy_code"), - Index(name = "pennies_idx", columnList = "pennies"))) + indexes = arrayOf(Index(name = "ccy_code_idx", columnList = "ccy_code"), + Index(name = "pennies_idx", columnList = "pennies"))) class PersistentCashState( /** X500Name of owner party **/ @Column(name = "owner_name") diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCommercialPaperSchemaV1.kt b/perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCommercialPaperSchemaV1.kt index 3fc657d1a1..54c12d99e1 100644 --- a/perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCommercialPaperSchemaV1.kt +++ b/perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCommercialPaperSchemaV1.kt @@ -21,9 +21,9 @@ object PtCommercialPaperSchema object PtCommercialPaperSchemaV1 : MappedSchema(schemaFamily = PtCommercialPaperSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCommercialPaperState::class.java)) { @Entity @Table(name = "cp_states", - indexes = arrayOf(Index(name = "ccy_code_index", columnList = "ccy_code"), - Index(name = "maturity_index", columnList = "maturity_instant"), - Index(name = "face_value_index", columnList = "face_value"))) + indexes = arrayOf(Index(name = "ccy_code_index", columnList = "ccy_code"), + Index(name = "maturity_index", columnList = "maturity_instant"), + Index(name = "face_value_index", columnList = "face_value"))) class PersistentCommercialPaperState( @Column(name = "issuance_key") var issuanceParty: String, diff --git a/perftestflows/src/main/resources/finance/contracts/LondonHolidayCalendar.txt b/perftestflows/src/main/resources/finance/contracts/LondonHolidayCalendar.txt deleted file mode 100644 index a68eaa7b30..0000000000 --- a/perftestflows/src/main/resources/finance/contracts/LondonHolidayCalendar.txt +++ /dev/null @@ -1 +0,0 @@ -2015-01-01,2015-04-03,2015-04-06,2015-05-04,2015-05-25,2015-08-31,2015-12-25,2015-12-28,2016-01-01,2016-03-25,2016-03-28,2016-05-02,2016-05-30,2016-08-29,2016-12-26,2016-12-27,2017-01-02,2017-04-14,2017-04-17,2017-05-01,2017-05-29,2017-08-28,2017-12-25,2017-12-26,2018-01-01,2018-03-30,2018-04-02,2018-05-07,2018-05-28,2018-08-27,2018-12-25,2018-12-26,2019-01-01,2019-04-19,2019-04-22,2019-05-06,2019-05-27,2019-08-26,2019-12-25,2019-12-26,2020-01-01,2020-04-10,2020-04-13,2020-05-04,2020-05-25,2020-08-31,2020-12-25,2020-12-28,2021-01-01,2021-04-02,2021-04-05,2021-05-03,2021-05-31,2021-08-30,2021-12-27,2021-12-28,2022-01-03,2022-04-15,2022-04-18,2022-05-02,2022-05-30,2022-08-29,2022-12-26,2022-12-27,2023-01-02,2023-04-07,2023-04-10,2023-05-01,2023-05-29,2023-08-28,2023-12-25,2023-12-26,2024-01-01,2024-03-29,2024-04-01,2024-05-06,2024-05-27,2024-08-26,2024-12-25,2024-12-26 \ No newline at end of file diff --git a/perftestflows/src/main/resources/finance/contracts/NewYorkHolidayCalendar.txt b/perftestflows/src/main/resources/finance/contracts/NewYorkHolidayCalendar.txt deleted file mode 100644 index e2edfa6902..0000000000 --- a/perftestflows/src/main/resources/finance/contracts/NewYorkHolidayCalendar.txt +++ /dev/null @@ -1 +0,0 @@ -2015-01-01,2015-01-19,2015-02-16,2015-02-18,2015-05-25,2015-07-03,2015-09-07,2015-10-12,2015-11-11,2015-11-26,2015-12-25,2016-01-01,2016-01-18,2016-02-10,2016-02-15,2016-05-30,2016-07-04,2016-09-05,2016-10-10,2016-11-11,2016-11-24,2016-12-26,2017-01-02,2017-01-16,2017-02-20,2017-03-01,2017-05-29,2017-07-04,2017-09-04,2017-10-09,2017-11-10,2017-11-23,2017-12-25,2018-01-01,2018-01-15,2018-02-14,2018-02-19,2018-05-28,2018-07-04,2018-09-03,2018-10-08,2018-11-12,2018-11-22,2018-12-25,2019-01-01,2019-01-21,2019-02-18,2019-03-06,2019-05-27,2019-07-04,2019-09-02,2019-10-14,2019-11-11,2019-11-28,2019-12-25,2020-01-01,2020-01-20,2020-02-17,2020-02-26,2020-05-25,2020-07-03,2020-09-07,2020-10-12,2020-11-11,2020-11-26,2020-12-25,2021-01-01,2021-01-18,2021-02-15,2021-02-17,2021-05-31,2021-07-05,2021-09-06,2021-10-11,2021-11-11,2021-11-25,2021-12-24,2022-01-17,2022-02-21,2022-03-02,2022-05-30,2022-07-04,2022-09-05,2022-10-10,2022-11-11,2022-11-24,2022-12-26,2023-01-02,2023-01-16,2023-02-20,2023-02-22,2023-05-29,2023-07-04,2023-09-04,2023-10-09,2023-11-10,2023-11-23,2023-12-25,2024-01-01,2024-01-15,2024-02-14,2024-02-19,2024-05-27,2024-07-04,2024-09-02,2024-10-14,2024-11-11,2024-11-28,2024-12-25 \ No newline at end of file diff --git a/perftestflows/src/main/resources/finance/utils/cities.txt b/perftestflows/src/main/resources/finance/utils/cities.txt deleted file mode 100644 index baf4c4f814..0000000000 --- a/perftestflows/src/main/resources/finance/utils/cities.txt +++ /dev/null @@ -1,1002 +0,0 @@ -# name longitude latitude -Tokyo (JP) 139.75 35.68 -Beijing Shi (CN) 116.38 39.92 -Shanghai (CN) 121.4 31 -Buenos Aires (AR) -58.67 -34.58 -Bombay (IN) 72.82 18.97 -Karachi (PK) 67.03 24.87 -Constantinople (TR) 28.95 41.02 -Mexico (MX) -99.03 19.38 -Delhi (IN) 77.22 28.67 -Manila (PH) 120.97 14.6 -Moscow (RU) 37.6 55.75 -Seoul (KR) 127 37.55 -Sao Paulo (BR) -46.62 -23.52 -Istanbul (TR) 28.95 41.02 -Lagos (NG) 3.38 6.45 -Mexico (MX) -99.13 19.43 -Jakarta (ID) 106.82 -6.17 -Edo (JP) 139.75 35.68 -New York (US) -74 40.7 -Kinshasa (CD) 15.3 -4.28 -Al Qahirah (EG) 31.25 30.05 -Lima (PE) -77.03 -12.05 -Peking (CN) 116.38 39.92 -London (GB) -0.12 51.5 -Tehran (IR) 51.42 35.67 -Bogota (CO) -74.07 4.58 -Hong Kong (HK) 114.15 22.27 -Dhaka (BD) 90.4 23.72 -Lahore (PK) 74.33 31.53 -Rio De Janeiro (BR) -43.22 -22.88 -Baghdad (IQ) 44.38 33.33 -Bangkok (TH) 100.52 13.75 -Bangalore (IN) 77.57 12.97 -Santiago (CL) -70.67 -33.45 -Calcutta (IN) 88.37 22.57 -Toronto (CA) -79.42 43.67 -Dagon (MM) 96.17 16.77 -Sydney (AU) 151.22 -33.87 -Madras (IN) 80.27 13.07 -Abia (NG) 7.87 5.52 -Wuhan (CN) 114.27 30.57 -Ogun (NG) 3.6 7.22 -Saint Petersburg (RU) 30.25 59.88 -Chongqing (CN) 106.55 29.55 -Xian (CN) 108.93 34.25 -Chengdu (CN) 104.07 30.67 -Chittagong (BD) 91.8 22.35 -Los Angeles (US) -118.23 34.05 -Alexandria (EG) 29.92 31.18 -Tianjin (CN) 117.17 39.13 -Melbourne (AU) 144.97 -37.82 -Ahmadabad (IN) 72.62 23.02 -Abidjan (CI) -4.02 5.33 -Busan (KR) 129.03 35.1 -Kano (NG) 8.52 11.98 -Casablanca (MA) -7.6 33.58 -Hyderabad (IN) 78.47 17.37 -Yokohama (JP) 139.65 35.45 -Ibadan (NG) 3.88 7.38 -Singapore (SG) 103.85 1.28 -Ankara (TR) 32.85 39.92 -Shenyang (CN) 123.42 41.78 -Riyadh (SA) 46.77 24.63 -Ho Chi Minh City (VN) 106.67 10.75 -Cape Town (ZA) 18.42 -33.92 -Berlin (DE) 13.4 52.52 -Montreal (CA) -73.57 45.5 -Harbin (CN) 126.65 45.75 -Pyongyang (KP) 125.75 39.02 -Guangzhou (CN) 113.25 23.12 -Durban (ZA) 31.02 -29.85 -Madrid (ES) -3.67 40.38 -Nanjing (CN) 118.77 32.05 -Kabul (AF) 69.17 34.52 -Pune (IN) 73.87 18.52 -Surat (IN) 72.9 20.97 -Chicago (US) -87.65 41.85 -Kanpur (IN) 80.33 26.47 -Umm Durman (SD) 32.43 15.63 -Luanda (AO) 13.23 -8.83 -Addis Abeba (ET) 38.7 9.02 -Nairobi (KE) 36.82 -1.27 -Taiyuan (CN) 112.47 37.72 -Jaipur (IN) 75.82 26.92 -Salvador (BR) -38.52 -12.97 -Dakar (SN) -17.43 14.67 -Dar Es Salaam (TZ) 39.27 -6.78 -Rome (IT) 12.47 41.88 -Yunfu (CN) 112.02 22.92 -Changwat Nakhon Rat Sima (TH) 102.12 14.97 -Al Basrah (IQ) 47.82 30.48 -Osaka (JP) 135.5 34.67 -Mogadishu (SO) 45.37 2.07 -Taegu (KR) 128.58 35.87 -Jiddah (SA) 39.22 21.52 -Changchun (CN) 125.32 43.87 -Taipei (TW) 121.52 25.03 -Kiev (UA) 30.52 50.42 -Faisalabad (PK) 73.07 31.42 -Izmir (TR) 27.15 38.4 -Lakhnau (IN) 80.92 26.85 -Konia (TR) 32.47 37.85 -Gizeh (EG) 31.2 30 -Ceara (BR) -38.5 -3.72 -Cali (CO) -76.52 3.43 -Surabaya (ID) 112.75 -7.23 -Brusa (TR) 29.05 40.18 -Belo Horizonte (BR) -43.92 -19.92 -Grand Dakar (SN) -17.45 14.7 -Al Kuwait (KW) 47.97 29.37 -Mashhad (IR) 59.6 36.28 -Brooklyn (US) -73.95 40.63 -Quezon (PH) 121.03 14.63 -Nagpur (IN) 79.08 21.13 -Harare (ZW) 31.03 -17.82 -Brasilia (BR) -47.92 -15.77 -Santo Domingo (DO) -69.9 18.47 -Nagoya (JP) 136.92 35.17 -Havana (CU) -82.35 23.12 -Kosovo (RS) 21.08 42.63 -Aleppo (SY) 37.15 36.2 -Paris (FR) 2.32 48.87 -Changsha (CN) 113.1 28.17 -Jinan (CN) 116.98 36.67 -Al Mawsil Al Jadidah (IQ) 43.08 36.32 -Tangshan (CN) 114.7 37.32 -Dalian (CN) 121.6 38.9 -Houston (US) -95.35 29.75 -Johannesburg (ZA) 28.07 -26.18 -Kowloon (HK) 114.17 22.32 -Al Basrah Al Qadimah (IQ) 47.82 30.48 -Zhengzhou (CN) 113.52 34.68 -Cheng (CN) 113.63 34.75 -Medellin (CO) -75.53 6.28 -Algiers (DZ) 3.05 36.75 -Tashkent (UZ) 69.25 41.32 -Al Jazair (DZ) 3.05 36.75 -Khartoum (SD) 32.53 15.58 -Accra (GH) -0.22 5.53 -Guayaquil (EC) -79.9 -2.17 -Maracaibo (VE) -71.63 10.62 -Rabat (MA) -6.83 34.02 -Sapporo (JP) 141.35 43.05 -Jilin (CN) 126.55 43.85 -Hangzhou (CN) 120.17 30.25 -Bucuresti (RO) 26.1 44.42 -Nanchang (CN) 115.92 28.55 -Camayenne (GN) -13.68 9.53 -Brisbane (AU) 153.02 -27.5 -Vancouver (CA) -123.12 49.25 -Indore (IN) 75.82 22.72 -Caracas (VE) -66.92 10.5 -Ecatepec (MX) -99.05 19.6 -Sanaa (YE) 44.2 15.35 -Changwat Chiang Mai (TH) 98.97 18.78 -Medan (ID) 98.67 3.57 -Rawalpindi (PK) 73.07 33.6 -Minsk (BY) 27.57 53.88 -Mosul (IQ) 43.12 36.33 -Hamburg (DE) 10 53.53 -Curitiba (BR) -49.25 -25.42 -Budapest (HU) 19.07 47.5 -Bandung (ID) 107.62 -6.9 -Soweto (ZA) 27.87 -26.27 -Edessa (TR) 38.78 37.15 -Warsaw (PL) 21 52.25 -Qingdao (CN) 120.37 36.08 -Guadalajara (MX) -103.32 20.67 -Pretoria (ZA) 28.22 -25.7 -Alep (SY) 37.15 36.2 -Patna (IN) 85.12 25.6 -Bhopal (IN) 77.4 23.27 -New Patna (IN) 85.12 25.6 -Manaus (BR) -60.02 -3.1 -Xinyang (CN) 114.12 32.08 -Kaduna (NG) 7.43 10.52 -Damascus (SY) 36.28 33.5 -Phnom Penh (KH) 104.92 11.55 -Barcelona (ES) 2.17 41.37 -Al-sham (SY) 36.28 33.5 -Wien (AT) 16.37 48.2 -Esfahan (IR) 51.67 32.65 -Ludhiana (IN) 75.83 30.88 -Changwat Nakhon Si Thammarat (TH) 99.97 8.42 -Kobe (JP) 135.17 34.67 -Bekasi (ID) 106.98 -6.23 -Kaohsiung (TW) 120.33 22.62 -Kaohsiung (TW) 120.3 22.62 -Ciudad Juarez (MX) -106.47 31.72 -Urumqi (CN) 87.57 43.78 -Changwat Chiang Rai (TH) 99.75 19.87 -Changwat Surin (TH) 103.42 14.82 -Thana (IN) 72.97 19.18 -Recife (BR) -34.88 -8.05 -Taejon (KR) 127.32 36.35 -Daejeon (KR) 127.42 36.32 -Kumasi (GH) -1.62 6.67 -Kyoto (JP) 135.75 35 -Kuala Lumpur (MY) 101.7 3.17 -Philadelphia (US) -75.15 39.95 -Karaj (IR) 51 35.82 -Perth (AU) 115.82 -31.92 -Cordoba (AR) -64.17 -31.38 -Multan (PK) 71.47 30.18 -Ha Noi (VN) 105.83 21.02 -Kharkiv (UA) 36.25 50 -Agra (IN) 78.02 27.17 -Phoenix (US) -112.07 33.43 -Tabriz (IR) 46.28 38.07 -Novosibirsk (RU) 82.93 55.03 -Lanzhou (CN) 103.78 36.05 -Kwangju (KR) 126.9 35.15 -Bursa (TR) 29.05 40.18 -Changwat Roi Et (TH) 103.67 16.05 -Vadodara (IN) 73.2 22.3 -Belem (BR) -48.47 -1.43 -Juarez (MX) -106.47 31.72 -Fushun (CN) 123.87 41.7 -Quito (EC) -78.5 -0.22 -Fukuoka (JP) 130.4 33.57 -Puebla (MX) -98.2 19.05 -Antananarivo (MG) 47.52 -18.92 -Luoyang (CN) 112.45 34.68 -Hefei (CN) 117.27 31.85 -Hyderabad (PK) 68.37 25.37 -Valencia (VE) -68 10.17 -Gujranwala (PK) 74.17 32.13 -Barranquilla (CO) -74.78 10.95 -Tijuana (MX) -117.02 32.52 -Lubumbashi (CD) 27.47 -11.67 -Porto Alegre (BR) -51.2 -30.02 -Tangerang (ID) 106.62 -6.17 -Santa Cruz De La Sierra (BO) -63.17 -17.8 -Handan (CN) 114.52 36.57 -Kampala (UG) 32.55 0.3 -Kocaeli (TR) 29.92 40.77 -Suzhou (CN) 120.62 31.3 -Khulna (BD) 89.53 22.8 -Douala (CM) 9.68 4.05 -Shantou (CN) 116.67 23.37 -Gorakhpur (IN) 75.67 29.43 -Makasar (ID) 119.43 -5.13 -Kawasaki (JP) 139.72 35.52 -Kawasaki (JP) 139.52 35.87 -Montevideo (UY) -56.17 -34.85 -Baotou Shi (CN) 109.82 40.65 -Medina (SA) 39.6 24.47 -Yaounde (CM) 11.52 3.87 -Bamako (ML) -8 12.65 -Changwat Songkhla (TH) 100.52 7.12 -Nasik (IN) 73.78 19.97 -Changwat Chon Buri (TH) 100.97 13.37 -Semarang (ID) 110.42 -6.97 -Yekaterinburg (RU) 60.6 56.85 -San Diego (US) -117.15 32.7 -Pimpri (IN) 73.78 18.62 -Nizhniy Novgorod (RU) 44 56.32 -Faridabad (IN) 77.32 28.42 -Davao City (PH) 125.6 7.07 -Amman (JO) 35.92 31.93 -Ciudad De Montevideo (UY) -56.17 -34.85 -Lusaka (ZM) 28.27 -15.42 -Katmandu (NP) 85.32 27.72 -Kalyan (IN) 73.15 19.25 -Thane (IN) 72.97 19.18 -San Antonio (US) -98.48 29.42 -Stockholm (SE) 18.05 59.32 -Beirut (LB) 35.5 33.87 -Shiraz (IR) 52.53 29.6 -Adana (TR) 35.32 37 -Munich (DE) 11.57 48.13 -Suwon (KR) 127.02 37.28 -Suigen (KR) 127 37.55 -Palembang (ID) 104.75 -2.92 -Port-au-prince (HT) -72.33 18.53 -Nezahualcoyotl (MX) -99.02 19.4 -Meerut (IN) 77.7 28.97 -Peshawar (PK) 71.57 34 -Rosario (AR) -60.65 -32.95 -Central (PH) 125.6 7.07 -Central (PH) 125.42 6.82 -Jaizan (SA) 42.55 16.88 -Davao (PH) 125.6 7.07 -Dallas (US) -96.78 32.77 -Mandalay (MM) 96.07 22 -Almaty (KZ) 76.95 43.25 -Spetsgorodok (KZ) 76.88 43.33 -Bronx (US) -73.87 40.85 -Changwat Sakon Nakhon (TH) 104.03 17.07 -Omdurman (SD) 32.43 15.63 -Mecca (SA) 39.82 21.42 -Ghaziabad (IN) 77.42 28.67 -Anshan (CN) 122.98 41.12 -Xuzhou (CN) 117.18 34.27 -Depok (ID) 106.82 -6.4 -Maputo (MZ) 32.58 -25.95 -Freetown (SL) -13.23 8.48 -Changwat Chaiyaphum (TH) 102.02 15.8 -Fuzhou (CN) 119.3 26.05 -Changwat Nakhon Sawan (TH) 100.12 15.67 -Rajkot (IN) 70.77 22.3 -Pest (HU) 19.07 47.5 -Guiyang (CN) 106.72 26.57 -Goiania (BR) -49.27 -16.67 -Guarulhos (BR) -46.52 -23.47 -Praha (CZ) 14.47 50.07 -Varanasi (IN) 83 25.32 -Fez (MA) -4.97 34.05 -Milan (IT) 9.18 45.47 -Changwat Phetchabun (TH) 101.15 16.35 -Tripoli (LY) 13.17 32.88 -Port Harcourt (NG) 6.98 4.78 -Baile Atha Cliath (IE) -6.23 53.32 -Hiroshima (JP) 132.43 34.38 -Dubayy (AE) 55.27 25.25 -Managua (NI) -86.27 12.15 -Dubai (AE) 55.27 25.25 -Samara (RU) 50.13 53.2 -Omsk (RU) 73.4 55 -Benin (NG) 5.62 6.32 -Monterrey (MX) -100.32 25.67 -Baku (AZ) 49.87 40.38 -Brazzaville (CG) 15.28 -4.25 -Belgrade (RS) 20.47 44.82 -Leon (MX) -101.67 21.12 -Maiduguri (NG) 13.15 11.83 -Wuxi (CN) 120.28 31.57 -Kazan (RU) 49.12 55.75 -Al Jadida (MA) -8.5 33.25 -Yerevan (AM) 44.5 40.17 -Amritsar (IN) 74.85 31.62 -Kaisaria (TR) 35.48 38.72 -Copenhagen (DK) 12.57 55.67 -Ouagadouga (BF) -1.52 12.37 -Changwat Kalasin (TH) 103.5 16.37 -Taichung (TW) 120.67 24.13 -Yono (JP) 139.62 35.87 -Rostov-na-donu (RU) 39.7 47.23 -Adelaide (AU) 138.58 -34.92 -Allahabad (IN) 81.83 25.43 -Gaziantep (TR) 37.37 37.05 -Visakhapatnam (IN) 83.28 17.68 -Chelyabinsk (RU) 61.42 55.15 -Sofia (BG) 23.32 42.67 -Unguja Ukuu (TZ) 39.37 -6.32 -Datong (CN) 113.58 40.03 -Dagu (CN) 113.28 40.08 -Tbilisi (GE) 44.78 41.72 -Changwat Nonthaburi (TH) 100.47 13.82 -Changwat Samut Prakan (TH) 100.58 13.58 -Sendai (JP) 140.88 38.25 -Xianyang (CN) 108.7 34.33 -Ufa (RU) 56.03 54.77 -Songnam (KR) 127.13 37.43 -Campinas (BR) -47.07 -22.88 -Ouagadougou (BF) -1.52 12.37 -Jabalpur (IN) 79.95 23.17 -Haora (IN) 88.3 22.58 -Huainan (CN) 116.98 32.62 -Dublin (IE) -6.23 53.32 -Kunming (CN) 102.72 25.03 -Brussels (BE) 4.32 50.82 -Aurangabad (IN) 75.32 19.87 -Qom (IR) 50.87 34.63 -Volgograd (RU) 44.58 48.8 -Aidin (TR) 27.83 37.83 -Shenzhen (CN) 114.12 22.52 -Nova Iguacu (BR) -43.43 -22.75 -Rongcheng (CN) 116.35 23.52 -Odesa (UA) 30.72 46.47 -Kitakyushu (JP) 130.82 33.82 -Sholapur (IN) 75.92 17.67 -Baoding (CN) 115.48 38.85 -Changwat Surat Thani (TH) 99.32 9.12 -Napoli (IT) 14.25 40.82 -Benxi (CN) 123.75 41.28 -Zapopan (MX) -103.4 20.72 -Birmingham (GB) -1.92 52.47 -Perm (RU) 56.25 58 -Naples (IT) 14.25 40.82 -Erzerum (TR) 41.27 39.9 -Srinagar (IN) 74.8 34.08 -Zaria (NG) 7.7 11.07 -Guatemala (GT) -90.52 14.62 -Mendoza (AR) -68.82 -32.87 -Changwat Phitsanulok (TH) 100.25 16.82 -Cologne (DE) 6.95 50.92 -Calgary (CA) -114.07 51.07 -Port Elizabeth (ZA) 25.57 -33.97 -Warab (SD) 28.62 8.12 -Fes (MA) -4.97 34.05 -Koeln (DE) 6.95 50.92 -Coimbatore (IN) 76.95 10.98 -Maceio (BR) -35.72 -9.67 -Cartagena (CO) -75.5 10.38 -Settat (MA) -7.62 33 -Changzhou (CN) 119.97 31.77 -Sultanah (SA) 39.57 24.5 -Ranchi (IN) 85.32 23.35 -Marrakesh (MA) -8 31.62 -Changwat Nong Khai (TH) 102.73 17.87 -Sao Goncalo (BR) -43.02 -22.8 -Maha Sarakam (TH) 103.28 16.17 -Changwat Maha Sarakham (TH) 103.22 16.17 -Monrovia (LR) -10.8 6.3 -Irbil (IQ) 44 36.18 -Malatia (TR) 38.3 38.35 -Jodhpur (IN) 73.02 26.28 -Chiba (JP) 140.12 35.6 -Sao Luis (BR) -44.27 -2.52 -Chandigarh (IN) 76.78 30.73 -Gampheang Phet (TH) 99.5 16.47 -Madurai (IN) 78.12 9.92 -Ad Diwaniyah (IQ) 44.92 31.98 -Krasnoyarsk (RU) 92.78 56 -Huaibei (CN) 116.78 33.97 -Cochabamba (BO) -66.15 -17.37 -Ghom (IR) 50.87 34.63 -Abu Ghurayb (IQ) 43.98 33.28 -Abobo (CI) -4.02 5.42 -Guwahati (IN) 91.72 26.17 -Aba (NG) 7.37 5.12 -San Jose (US) -121.88 37.33 -Bulawayo (ZW) 28.57 -20.13 -Bishkek (KG) 74.6 42.87 -Pingdingshan (CN) 113.3 33.73 -Detroit (US) -83.03 42.32 -Changwat Rat Buri (TH) 99.78 13.52 -Gwalior (IN) 78.17 26.22 -Qiqihar (CN) 123.97 47.33 -Klang (MY) 101.45 3.02 -Safi (MA) -9.23 32.28 -Konya (TR) 32.47 37.85 -Mbuji-mayi (CD) 23.6 -6.15 -Vijayawada (IN) 80.62 16.52 -Ottawa (CA) -75.7 45.42 -Maisuru (IN) 76.63 12.3 -Changwat Suphan Buri (TH) 100.12 14.47 -Wenzhou (CN) 120.65 28.02 -Torino (IT) 7.67 45.03 -Saratov (RU) 46.02 51.57 -Ahvaz (IR) 48.68 31.32 -Tegucigalpa (HN) -87.22 14.08 -Turin (IT) 7.67 45.03 -Naucalpan (MX) -99.23 19.47 -Da Huryee (MN) 106.92 47.92 -Arequipa (PE) -71.53 -16.38 -Voronezh (RU) 39.17 51.65 -Padang (ID) 100.33 -0.95 -Hubli (IN) 75.17 15.33 -Marrakech (MA) -8 31.62 -Callao (PE) -77.15 -12.07 -Lvov (UA) 24 49.82 -Tucuman (AR) -65.22 -26.82 -Tangier (MA) -5.8 35.78 -Changwat Lampang (TH) 99.5 18.28 -Edmonton (CA) -113.5 53.53 -Duque De Caxias (BR) -43.3 -22.78 -Jos (NG) 8.9 9.92 -Sale (MA) -6.8 34.03 -Ilorin (NG) 4.53 8.5 -La Paz (BO) -68.15 -16.5 -Barquisimeto (VE) -69.32 10.07 -Oslo (NO) 10.75 59.92 -Nanning (CN) 108.32 22.82 -Johor Bahru (MY) 103.75 1.47 -Bandar Lampung (ID) 105.27 -5.45 -Cebu City (PH) 123.88 10.3 -Mombasa (KE) 39.67 -4.03 -Asgabat (TM) 58.37 37.95 -Jacksonville (US) -81.65 30.32 -Aleksandrovsk (UA) 35.17 47.82 -Lobh Buri (TH) 100.62 14.8 -Marseille (FR) 5.4 43.28 -Nagara Pathom (TH) 100.03 13.82 -Kathmandu (NP) 85.32 27.72 -Rupandehi (NP) 83.27 27.47 -Jalandhar (IN) 75.57 31.32 -Thiruvananthapuram (IN) 76.95 8.5 -Sakai (JP) 135.47 34.57 -Anyang (CN) 114.32 36.08 -San Miguel De Tucuman (AR) -65.22 -26.82 -Changwat Phra Nakhon Si Ayutthaya (TH) 100.53 14.33 -Selam (IN) 78.17 11.65 -Taroudannt (MA) -8.87 30.47 -Tiruchchirappalli (IN) 78.68 10.8 -Hims (SY) 36.72 34.72 -Hohhot (CN) 111.65 40.8 -Niamey (NE) 2.12 13.52 -Niamey (NE) 1.77 13.67 -Indianapolis (US) -86.15 39.77 -Valencia (ES) -0.37 39.47 -Changwat Nakhon Phanom (TH) 104.72 17.37 -Bogor (ID) 106.78 -6.58 -Lodz (PL) 19.47 51.75 -Ad Dammam (SA) 50.1 26.42 -Padumdhani (TH) 100.52 14.02 -Xining (CN) 101.77 36.62 -Kermanshah (IR) 47.05 34.3 -Kahriz (IR) 47.12 34.3 -Liuzhou (CN) 109.38 24.3 -Kota (IN) 75.82 25.17 -Natal (BR) -35.22 -5.77 -Bhubaneswar (IN) 85.82 20.22 -Qinhuangdao (CN) 119.58 39.92 -Hengyang (CN) 112.6 26.88 -Antalya (TR) 30.68 36.9 -Cebu (PH) 123.88 10.3 -Adalia (TR) 30.68 36.9 -Krakow (PL) 19.92 50.07 -Aligarh (IN) 78.07 27.87 -Da Nang (VN) 108.22 16.07 -Naradhivas (TH) 101.82 6.42 -Pietermaritzburg (ZA) 30.37 -29.62 -Taian (CN) 117.12 36.18 -Trujillo (PE) -79.02 -8.12 -Lome (TG) 1.22 6.12 -Lome (TG) 1 6.48 -Malang (ID) 112.62 -7.97 -Ciudad Guayana (VE) -62.65 8.35 -Amsterdam (NL) 4.92 52.35 -Kigali (RW) 30.05 -1.95 -Bareli (IN) 79.42 28.35 -Kigali (RW) 29.93 -2.28 -Kigale (RW) 30.05 -1.95 -Teresina (BR) -42.82 -5.07 -Poti (BR) -42.82 -5.02 -Xinxiang (CN) 113.87 35.3 -Sao Bernardo Do Campo (BR) -46.53 -23.68 -Hegang (CN) 130.37 47.38 -Riga (LV) 24.1 56.95 -Taza (MA) -4.02 34.22 -Astrida (RW) 29.73 -2.58 -Columbus (US) -82.98 39.95 -Oyo (NG) 3.92 7.83 -Tainan (TW) 120.2 22.98 -Quetta (PK) 67 30.18 -San Francisco (US) -122.42 37.77 -Jhapa (NP) 87.83 26.47 -Campo Grande (BR) -54.62 -20.43 -Athens (GR) 23.72 37.97 -Ashgabat (TM) 58.37 37.95 -Sar-e Pol (AF) 66.7 35.53 -Guadalupe (MX) -100.25 25.67 -As Sulaymaniyah (IQ) 45.43 35.55 -Dhanukha (NP) 86.07 26.82 -Cucuta (CO) -72.5 7.87 -Moradabad (IN) 78.77 28.82 -Langfang (CN) 116.68 39.5 -Ningbo (CN) 121.53 29.87 -Yantai (CN) 121.4 37.52 -Tolyatti (RU) 49.4 53.52 -Merida (MX) -89.62 20.97 -Tlalnepantla (MX) -99.22 19.52 -Jerusalem (IL) 35.22 31.77 -Chisinau (MD) 28.85 47 -Kailali (NP) 80.77 28.57 -Chonju (KR) 127.13 35.82 -Nouakchott (MR) -15.97 18.08 -Zhuzhou (CN) 113.12 27.68 -Chihuahua (MX) -106.07 28.62 -Bhiwandi (IN) 73.07 19.3 -Jaboatao (BR) -35.02 -8.12 -Rajshahi (BD) 88.58 24.37 -Zagreb (HR) 16 45.78 -Agadair (MA) -9.58 30.38 -Bosna-sarai (BA) 18.37 43.85 -Eva Peron (AR) -57.93 -34.92 -Tunes (TN) 10.17 36.8 -Zhangjiakou (CN) 114.87 40.8 -Cluj (RO) 23.6 46.77 -Cotonou (BJ) 2.42 6.33 -Zigong (CN) 104.77 29.38 -Fuxin (CN) 121.65 42 -Enuga (NG) 7.47 6.42 -Tanger (MA) -5.8 35.78 -Liaoyang (CN) 123.05 41.22 -Sevilla (ES) -5.98 37.37 -Nerima (JP) 139.65 35.72 -La Plata (AR) -57.93 -34.92 -Bangui (CF) 18.57 4.37 -Kumamoto (JP) 130.72 32.78 -Raipur (IN) 81.62 21.22 -Austin (US) -97.73 30.27 -Adiyaman (TR) 38.27 37.75 -Osasco (BR) -46.77 -23.57 -San Luis Potosi (MX) -100.97 22.13 -Leui (TH) 101.72 17.47 -Changwat Loei (TH) 101.72 17.42 -Gorakhpur (IN) 83.37 26.75 -Ipoh (MY) 101.07 4.57 -Zibo (CN) 118.05 36.78 -Palermo (IT) 13.37 38.12 -Changwat Chachoengsao (TH) 101.07 13.68 -Mississauga (CA) -79.5 43.13 -Taounate (MA) -4.65 34.53 -Puyang (CN) 115 35.7 -Nantong (CN) 120.87 32.02 -Changwat Sukhothai (TH) 99.82 17 -Changwat Sawankhalok (TH) 99.83 17.3 -Mudanjiang (CN) 129.58 44.57 -Santo Andre (BR) -46.52 -23.67 -Pointe-noire (CG) 11.83 -4.78 -Aguascalientes (MX) -102.28 21.87 -Agadir (MA) -9.58 30.38 -Hamilton (CA) -79.82 43.25 -Enugu (NG) 7.47 6.42 -Kryvyy Rih (UA) 33.35 47.92 -Acapulco (MX) -99.92 16.85 -Joao Pessoa (BR) -34.87 -7.12 -Ansan (KR) 126.82 37.32 -Benghazi (LY) 20.07 32.12 -Memphis (US) -90.13 34.92 -Frankfurt Am Main (DE) 8.67 50.12 -Krasnodar (RU) 38.97 45.02 -Shaoyang (CN) 111.25 27 -Guilin (CN) 110.27 25.27 -Sagamihara (JP) 139.35 35.55 -Zamboanga City (PH) 122.07 6.9 -Colombo (LK) 79.83 6.92 -Frankfurt (DE) 8.67 50.12 -Lilongwe (MW) 33.77 -13.97 -Wahran (DZ) -0.63 35.68 -Mar Del Plata (AR) -57.53 -38 -Quebec (CA) -71.25 46.78 -Diyarbakir (TR) 40.2 37.92 -New South Memphis (US) -90.05 35.08 -Memphis (US) -90.03 35.13 -Ulyanovsk (RU) 48.38 54.32 -Okayama (JP) 133.92 34.63 -Zhanjiang (CN) 110.37 21.18 -Yogyakarta (ID) 110.37 -7.78 -Zaragoza (ES) -0.87 41.62 -Wroclaw (PL) 17.02 51.1 -Anyang (KR) 126.92 37.38 -Zhenjiang (CN) 119.43 32.2 -Winnipeg (CA) -97.17 49.87 -Dandong (CN) 124.38 40.12 -Izhevsk (RU) 53.22 56.85 -Shaoguan (CN) 113.57 24.8 -Yancheng (CN) 120.12 33.38 -Foshan (CN) 113.12 23.02 -Contagem (BR) -44.1 -19.92 -Bhilai (IN) 81.42 21.22 -Panshan (CN) 122.03 41.18 -Jibuti (DJ) 43.13 11.58 -Saltillo (MX) -101 25.42 -Ash Shariqah (AE) 55.38 25.35 -Fort Worth (US) -97.32 32.72 -El-hodeidah (YE) 42.95 14.78 -Jamshedpur (IN) 86.17 22.8 -Tandjile (TD) 16.1 9.32 -Haikou (CN) 110.33 20.03 -Changwat Phichit (TH) 100.37 16.42 -Sao Jose Dos Campos (BR) -45.87 -23.17 -Changwat Trang (TH) 99.58 7.53 -Mersin (TR) 34.63 36.72 -Taizhou (CN) 119.9 32.48 -Queretaro (MX) -100.37 20.6 -Xingtai (CN) 114.48 37.05 -Baltimore (US) -76.6 39.28 -Glasgow (GB) -4.25 55.82 -Yaroslavl (RU) 39.87 57.62 -Elazig (TR) 39.22 38.67 -Benoni (ZA) 28.32 -26.17 -Hamamatsu (JP) 137.72 34.7 -Kochi (IN) 76.22 9.97 -Amravati (IN) 77.75 20.92 -Rotterdam (NL) 4.5 51.92 -Amaravati (IN) 77.75 20.92 -Abu Dhabi (AE) 54.37 24.47 -Hai Phong (VN) 106.67 20.85 -Orumiyeh (IR) 45.1 37.55 -Genova (IT) 8.93 44.42 -Islamabad (PK) 73.17 33.7 -Kirkuk (IQ) 44.38 35.47 -Barnaul (RU) 83.75 53.35 -Charlotte (US) -80.83 35.22 -El Paso (US) -106.48 31.75 -Luancheng (CN) 114.65 37.87 -Mexicali (MX) -115.47 32.65 -Hermosillo (MX) -110.97 29.07 -Rasht (IR) 49.58 37.27 -Dortmund (DE) 7.45 51.52 -Kayseri (TR) 35.48 38.72 -Abeokuta (NG) 3.35 7.15 -Caesarea (TR) 35.48 38.72 -Morelia (MX) -101.12 19.68 -Stuttgart (DE) 9.17 48.77 -Yingkou (CN) 122.5 40.63 -Eiko (CN) 122.22 40.67 -Chimalhuacan (MX) -98.9 19.42 -Zhangzhou (CN) 117.65 24.5 -Vladivostok (RU) 131.9 43.12 -Irkutsk (RU) 104.32 52.27 -Belfast (GB) -5.92 54.57 -Genoa (IT) 8.93 44.42 -Blantyre (MW) 35 -15.77 -Kingston (JM) -76.78 18 -Chiclayo (PE) -79.83 -6.77 -Culiacan (MX) -107.38 24.78 -Cuttack (IN) 85.82 20.5 -Hachioji (JP) 139.32 35.65 -Milwaukee (US) -87.9 43.03 -Xiamen (CN) 118.08 24.47 -Khabarovsk (RU) 135.08 48.5 -Ussuriyskiy (RU) 135.05 48.43 -Khabarovsk Vtoroy (RU) 135.13 48.43 -Libreville (GA) 9.43 0.37 -Kerman (IR) 57.08 30.28 -Dusseldorf (DE) 6.77 51.22 -Kaifeng (CN) 114.42 34.73 -Essen (DE) 7.02 51.45 -Bengbu (CN) 117.35 32.93 -Bikaner (IN) 73.28 28.02 -Banjarmasin (ID) 114.57 -3.32 -Shihezi (CN) 86.02 44.28 -Bouake (CI) -5.02 7.67 -Bucaramanga (CO) -73.12 7.12 -South Boston (US) -71.03 42.32 -Kuching (MY) 110.32 1.55 -Poznan (PL) 16.97 52.42 -Seattle (US) -122.32 47.6 -Veracruz (MX) -96.12 19.18 -Lisboa (PT) -9.12 38.72 -Asmara (ER) 38.92 15.32 -Sokoto (NG) 5.22 13.05 -Uberlandia (BR) -48.28 -18.92 -Onitsha (NG) 6.77 6.17 -Onicha (NG) 6.4 5.82 -Funabashi (JP) 139.97 35.68 -Hamhung (KP) 127.53 39.9 -Sorocaba (BR) -47.45 -23.47 -Helsinki (FI) 24.93 60.17 -Malaga (ES) -4.42 36.72 -Warangal (IN) 79.57 18 -Denver (US) -104.98 39.73 -Santiago (DO) -70.7 19.43 -Santiago De Cuba (CU) -75.82 20.02 -Surakarta (ID) 110.82 -7.57 -Kagoshima (JP) 130.55 31.6 -Huaiyin (CN) 119.02 33.58 -Bhavnagar (IN) 72.15 21.77 -Mar De Plata (AR) -57.53 -38 -Bahawalpur (PK) 71.67 29.38 -Washington (US) -77.03 38.88 -Zahedan (IR) 60.87 29.48 -Changwat Prachuap Khiri Khan (TH) 99.72 11.75 -Ribeirao Preto (BR) -47.78 -21.17 -Hamitabat (TR) 30.55 37.75 -Aden (YE) 45.03 12.77 -Chkalov (RU) 55.37 51.75 -Orenburgskiy (RU) 54.92 51.88 -Orenburg (RU) 55.08 51.78 -Jiamusi (CN) 130.33 46.82 -Antipolo (PH) 121.37 14.12 -Antipolo (PH) 121.17 14.58 -Salta (AR) -65.42 -24.77 -Chandaburi (TH) 102.15 12.58 -Neijiang (CN) 105.05 29.58 -Bremen (DE) 8.8 53.07 -Meknes (MA) -5.53 33.88 -Sharjah (AE) 55.38 25.35 -Matola (MZ) 32.45 -25.95 -Changwat Yasothon (TH) 104.13 15.78 -Al Sharjah (AE) 55.38 25.35 -Djuschambe (TJ) 68.77 38.55 -Sargodha (PK) 72.67 32.08 -Vilnius (LT) 25.32 54.67 -Cancun (MX) -86.82 21.17 -Portland (US) -122.67 45.52 -Maanshan (CN) 118.47 31.72 -Las Vegas (US) -115.13 36.17 -Changwat Tak (TH) 99.12 16.87 -Yangzhou (CN) 119.43 32.38 -Novokuznetsk (RU) 87.08 53.75 -Kisangani (CD) 25.18 0.52 -Warri (NG) 5.75 5.52 -Yongkang (CN) 120.02 28.87 -Tanggu (CN) 117.63 39.02 -Oklahoma City (US) -97.5 35.47 -Jiangmen (CN) 113.07 22.57 -Changwat Nan (TH) 100.7 18.68 -Nashville (US) -86.78 36.15 -Beira (MZ) 34.83 -19.83 -Guntur (IN) 80.45 16.3 -Yueyang (CN) 113.1 29.13 -Cangzhou (CN) 116.87 38.32 -Vaucluse (FR) 5.12 43.92 -San Salvador (SV) -89.2 13.7 -Changwat Yala (TH) 101.25 6.5 -Torreon (MX) -103.42 25.55 -Dehra Dun (IN) 78.02 30.32 -Cuiaba (BR) -56.07 -15.57 -Khemisset (MA) -6.07 33.82 -Lopez Mateos (MX) -99.25 19.55 -Petaling Jaya (MY) 101.65 3.07 -Ryazan (RU) 39.73 54.62 -Hanover (DE) 9.72 52.37 -Tyumen (RU) 65.52 57.15 -Durgapur (IN) 87.32 23.47 -Tucson (US) -110.92 32.22 -Quilmes (AR) -58.27 -34.72 -Ajmer (IN) 74.62 26.43 -Felicitas Julia (PT) -9.12 38.72 -Changde (CN) 111.67 29.02 -Jiaozuo (CN) 113.22 35.23 -Ulhasnagar (IN) 73.15 19.22 -Kolhapur (IN) 74.22 16.68 -Lipetsk (RU) 39.57 52.62 -Shiliguri (IN) 88.42 26.7 -Goteborg (SE) 11.97 57.72 -Eskisehir (TR) 30.52 39.77 -Hamadan (IR) 48.5 34.78 -Azadshahr (IR) 48.53 34.78 -Penza (RU) 45 53.18 -Changwat Phatthalung (TH) 99.97 7.57 -Changwat Chumphon (TH) 99.17 10.5 -Tembisa (ZA) 28.22 -25.98 -Changwat Uttaradit (TH) 100.1 17.62 -Nikolaev (UA) 32 46.97 -Khenifra (MA) -5.67 32.93 -Naberezhnyye Morkvashi (RU) 48.83 55.75 -Naberezhnyye Chelny (RU) 52.42 55.75 -Asuncion (PY) -57.67 -25.27 -San Nicolas De Los Garza (MX) -100.28 25.75 -Wuhu (CN) 118.53 31.17 -Dang (NP) 82.28 28.12 -Toluca (MX) -99.67 19.28 -Niigata (JP) 139.05 37.92 -Duisburg (DE) 6.75 51.42 -Asansol (IN) 86.97 23.67 -Azilal (MA) -6.57 31.97 -Asanol (IN) 86.97 23.67 -Arak (IR) 49.68 34.08 -Astrakhan (RU) 48.03 46.33 -Cagayan De Oro City (PH) 124.63 8.47 -Zhuhai (CN) 113.57 22.27 -Gold Coast (AU) 153.42 -28 -Wahren (DE) 12.32 51.37 -Bejraburi (TH) 99.95 13.08 -Oshogbo (NG) 4.57 7.77 -Las Pinas (PH) 120.97 14.45 -Shashi (CN) 112.23 30.3 -Reynosa (MX) -98.27 26.07 -Makhachkala (RU) 47.5 42.97 -Newcastle (AU) 151.75 -32.92 -Nuremberg (DE) 11.07 49.43 -Khouribga (MA) -6.9 32.87 -Ouarzazate (MA) -6.92 30.92 -Chimahi (ID) 107.53 -6.87 -Tlaquepaque (MX) -103.32 20.63 -Taguig (PH) 121.07 14.52 -Leipzig (DE) 12.32 51.28 -Jamnagar (IN) 70.07 22.47 -Panchiao (TW) 121.52 25.03 -Cibitoke (BI) 29.37 -3.33 -Aracaju (BR) -37.07 -10.92 -San Pedro Sula (HN) -88.02 15.5 -As Suways (EG) 32.53 29.97 -Albuquerque (US) -106.65 35.08 -Tomsk (RU) 84.97 56.5 -Matsuyama (JP) 132.75 33.83 -Nanded (IN) 77.32 19.13 -Saharanpur (IN) 77.53 29.97 -Gulbarga (IN) 76.82 17.32 -Bhatpara (IN) 88.4 22.87 -Long Beach (US) -118.18 33.77 -An Najaf (IQ) 44.3 31.98 -Feira De Santana (BR) -38.95 -12.25 -Shah Alam (MY) 101.52 3.07 -Himeji (JP) 134.68 34.82 -Tuxtla Gutierrez (MX) -93.12 16.75 -Gomel (BY) 30.97 52.43 -Dresden (DE) 13.75 51.03 -Okene (NG) 6.22 7.53 -Uijongbu (KR) 127.03 37.73 -Hargeysa (SO) 44.07 9.57 -Yazd (IR) 54.37 31.88 -Hargeisa (SO) 44.07 9.57 -Sialkot (PK) 74.52 32.5 -Kemerovo (RU) 86.07 55.32 -Yichang (CN) 111.28 30.7 -The Hague (NL) 4.28 52.07 -Tipaza (DZ) 2.43 36.58 -Cuautitlan Izcalli (MX) -99.23 19.63 -Yinchuan (CN) 106.27 38.47 -Skopje (MK) 21.42 42 -Vereeniging (ZA) 27.92 -26.67 -Maoming (CN) 110.9 21.63 -Londrina (BR) -51.13 -23.3 -Larache (MA) -6.15 35.18 -Jiaojiang (CN) 121.43 28.67 -Matsudo (JP) 139.9 35.77 -Juiz De Fora (BR) -43.35 -21.75 -San Juan (AR) -68.53 -31.53 -Liverpool (GB) -3 53.42 -Nishinomiya (JP) 135.32 34.72 -Tula (RU) 37.6 54.2 -Kawaguchi (JP) 139.72 35.8 -Sacramento (US) -121.48 38.57 -Shizuoka (JP) 138.37 34.97 -Zunyi (CN) 106.82 27.53 -Jiaxing (CN) 120.73 30.75 -Belford Roxo (BR) -43.38 -22.75 -Jammu (IN) 74.87 32.72 -Dongliao (CN) 125.13 42.9 -Fresno (US) -119.77 36.73 -Lyon (FR) 4.83 45.75 -Kananga (CD) 22.42 -5.88 -Bloemfontein (ZA) 26.18 -29.12 -Xiangfan (CN) 112.13 32.03 -Gdansk (PL) 18.67 54.35 -Calabar (NG) 8.32 4.95 -Panzhihua (CN) 101.72 26.55 -Joinville (BR) -48.82 -26.3 -Zamboanga (PH) 122.07 6.9 -Hamah (SY) 36.75 35.12 -Mixco (GT) -90.6 14.62 -Antwerp (BE) 4.42 51.22 -General Santos City (PH) 125.17 6.1 -Boayan (PH) 125.23 6.1 -New Orleans (US) -90.07 29.95 -Kanazawa (JP) 136.65 36.57 -Ichikawa (JP) 139.92 35.72 -Burleigh School (PH) 122.07 6.9 -Ujjain (IN) 75.77 23.17 -Kirov (RU) 49.65 58.58 -Kota Kinabalu (MY) 116.07 5.97 -Durango (MX) -104.67 24.02 -Niteroi (BR) -43.08 -22.88 -Hengshui (CN) 115.7 37.72 -Chitungwiza (ZW) 31.03 -17.98 -Santa Fe (AR) -60.7 -31.62 -Gabouk (SA) 36.57 28.37 -Pontianak (ID) 109.32 -0.02 -Leeds (GB) -1.57 53.78 -Bacolod City (PH) 122.95 10.65 -Sao Joao De Meriti (BR) -43.35 -22.8 -Mansilingan (PH) 122.97 10.62 -Bacolod (PH) 122.95 10.65 -Essaouira (MA) -9.77 31.5 -Manado (ID) 124.83 1.48 -Jining (CN) 116.57 35.4 -Constantine (DZ) 6.6 36.35 -Mesa (US) -111.82 33.42 -Utsunomiya (JP) 139.87 36.53 -Urfa (TR) 38.78 37.15 -Cleveland (US) -81.68 41.48 -Virginia Beach (US) -75.97 36.85 -Chengde (CN) 118.17 40.77 -Xuchang (CN) 113.82 34.02 -Oita (JP) 131.6 33.23 -Sheffield (GB) -1.5 53.37 -Cheboksary (RU) 47.25 56.12 -Cagayan De Oro (PH) 124.63 8.47 -Boksburg (ZA) 28.25 -26.22 -Kalat (AF) 66.9 32.1 -Rajpur (IN) 88.42 22.4 -Changwat Krabi (TH) 98.92 8.07 -Amagasaki (JP) 135.42 34.72 -Malatya (TR) 38.3 38.35 -North Kansas City (US) -94.55 39.12 -Kansas City (US) -94.57 39.08 -Kansas City (US) -94.62 39.1 -Dasmarinas (PH) 120.93 14.32 -Dasmarinas (PH) 121.02 14.53 -Nangi (IN) 88.2 22.5 -Pereira (CO) -75.68 4.8 -Calicut (IN) 75.77 11.25 -Carrefour (HT) -72.4 18.53 -Iquitos (PE) -73.23 -3.73 -Mawlamyine (MM) 97.62 16.48 -Baoji (CN) 107.37 34.35 -Kurashiki (JP) 133.77 34.57 -Garoua (CM) 13.4 9.3 -Mwanza (TZ) 32.88 -2.52 -Kousseri (CM) 15.02 12.07 -Tirunelveli (IN) 77.7 8.72 -Edinburgh (GB) -3.2 55.95 -Fort Fureau (CM) 15.02 12.07 -Malegaon (IN) 74.52 20.55 -Matamoros (MX) -97.5 25.87 -Kaliningrad (RU) 20.5 54.7 -Geneve (CH) 6.17 46.2 -Ananindeua (BR) -48.37 -1.37 -Balikpapan (ID) 116.82 -1.27 -Brampton (CA) -79.77 43.67 -Dadiangas (PH) 125.17 6.1 -Namangan (UZ) 71.67 40.98 -Katsina (NG) 7.58 12.98 -Welkom (ZA) 26.72 -27.97 -Santa Marta (CO) -74.2 11.23 -El Mahalla El Kubra (EG) 31.17 30.97 -Bristol (GB) -2.57 51.45 -Yokosuka (JP) 139.67 35.28 -Akola (IN) 77 20.72 -Belgaum (IN) 74.5 15.87 -# These have been added back by hand -Cairo (EG) 31.25 30.06 -Zurich (CH) 8.55 47.36 diff --git a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/PtCommercialPaperTests.kt b/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/PtCommercialPaperTests.kt index c1104548f5..decb9cb494 100644 --- a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/PtCommercialPaperTests.kt +++ b/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/PtCommercialPaperTests.kt @@ -26,30 +26,17 @@ import kotlin.test.assertTrue // TODO: The generate functions aren't tested by these tests: add them. -interface IPtCommercialPaperTestTemplate { - fun getPaper(): IPtCommercialPaperState +interface PtCommercialPaperTestTemplate { + fun getPaper(): PtCommercialPaper.State fun getIssueCommand(notary: Party): CommandData fun getRedeemCommand(notary: Party): CommandData fun getMoveCommand(): CommandData fun getContract(): ContractClassName } -class JavaCommercialPaperTest : IPtCommercialPaperTestTemplate { - override fun getPaper(): IPtCommercialPaperState = JavaPtCommercialPaper.State( - MEGA_CORP.ref(123), - MEGA_CORP, - 1000.DOLLARS `issued by` MEGA_CORP.ref(123), - TEST_TX_TIME + 7.days - ) - override fun getIssueCommand(notary: Party): CommandData = JavaPtCommercialPaper.Commands.Issue() - override fun getRedeemCommand(notary: Party): CommandData = JavaPtCommercialPaper.Commands.Redeem() - override fun getMoveCommand(): CommandData = JavaPtCommercialPaper.Commands.Move() - override fun getContract() = JavaPtCommercialPaper.JCP_PROGRAM_ID -} - -class KotlinCommercialPaperTest : IPtCommercialPaperTestTemplate { - override fun getPaper(): IPtCommercialPaperState = PtCommercialPaper.State( +class KotlinCommercialPaperTest : PtCommercialPaperTestTemplate { + override fun getPaper(): PtCommercialPaper.State = PtCommercialPaper.State( issuance = MEGA_CORP.ref(123), owner = MEGA_CORP, faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123), @@ -62,8 +49,8 @@ class KotlinCommercialPaperTest : IPtCommercialPaperTestTemplate { override fun getContract() = PtCommercialPaper.CP_PROGRAM_ID } -class KotlinCommercialPaperLegacyTest : IPtCommercialPaperTestTemplate { - override fun getPaper(): IPtCommercialPaperState = PtCommercialPaper.State( +class KotlinCommercialPaperLegacyTest : PtCommercialPaperTestTemplate { + override fun getPaper(): PtCommercialPaper.State = PtCommercialPaper.State( issuance = MEGA_CORP.ref(123), owner = MEGA_CORP, faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123), @@ -80,11 +67,11 @@ class KotlinCommercialPaperLegacyTest : IPtCommercialPaperTestTemplate { class CommercialPaperTestsGeneric { companion object { @Parameterized.Parameters @JvmStatic - fun data() = listOf(JavaCommercialPaperTest(), KotlinCommercialPaperTest(), KotlinCommercialPaperLegacyTest()) + fun data() = listOf(KotlinCommercialPaperTest(), KotlinCommercialPaperLegacyTest()) } @Parameterized.Parameter - lateinit var thisTest: IPtCommercialPaperTestTemplate + lateinit var thisTest: PtCommercialPaperTestTemplate val issuer = MEGA_CORP.ref(123) @@ -100,7 +87,7 @@ class CommercialPaperTestsGeneric { // Some CP is issued onto the ledger by MegaCorp. transaction("Issuance") { - attachments(CP_PROGRAM_ID, JavaPtCommercialPaper.JCP_PROGRAM_ID) + attachments(CP_PROGRAM_ID, PtCommercialPaper.CP_PROGRAM_ID) output(thisTest.getContract(), "paper") { thisTest.getPaper() } command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) } timeWindow(TEST_TX_TIME) @@ -110,11 +97,11 @@ 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(PtCash.PROGRAM_ID, JavaPtCommercialPaper.JCP_PROGRAM_ID) + attachments(PtCash.PROGRAM_ID, PtCommercialPaper.CP_PROGRAM_ID) input("paper") input("alice's $900") output(PtCash.PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP } - output(thisTest.getContract(), "alice's paper") { "paper".output().withOwner(ALICE) } + output(thisTest.getContract(), "alice's paper") { "paper".output().withOwner(ALICE) } command(ALICE_PUBKEY) { PtCash.Commands.Move() } command(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() } this.verifies() @@ -123,7 +110,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, JavaPtCommercialPaper.JCP_PROGRAM_ID) + attachments(CP_PROGRAM_ID, PtCommercialPaper.CP_PROGRAM_ID) input("alice's paper") input("some profits") @@ -150,7 +137,7 @@ class CommercialPaperTestsGeneric { timeWindow(TEST_TX_TIME + 8.days) tweak { - output(thisTest.getContract()) { "paper".output() } + output(thisTest.getContract()) { "paper".output() } this `fails with` "must be destroyed" } @@ -163,7 +150,7 @@ class CommercialPaperTestsGeneric { fun `key mismatch at issue`() { transaction { attachment(CP_PROGRAM_ID) - attachment(JavaPtCommercialPaper.JCP_PROGRAM_ID) + attachment(CP_PROGRAM_ID) output(thisTest.getContract()) { thisTest.getPaper() } command(MINI_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) } timeWindow(TEST_TX_TIME) @@ -175,7 +162,7 @@ class CommercialPaperTestsGeneric { fun `face value is not zero`() { transaction { attachment(CP_PROGRAM_ID) - attachment(JavaPtCommercialPaper.JCP_PROGRAM_ID) + attachment(CP_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) @@ -187,7 +174,7 @@ class CommercialPaperTestsGeneric { fun `maturity date not in the past`() { transaction { attachment(CP_PROGRAM_ID) - attachment(JavaPtCommercialPaper.JCP_PROGRAM_ID) + attachment(CP_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) @@ -199,7 +186,7 @@ class CommercialPaperTestsGeneric { fun `issue cannot replace an existing state`() { transaction { attachment(CP_PROGRAM_ID) - attachment(JavaPtCommercialPaper.JCP_PROGRAM_ID) + attachment(CP_PROGRAM_ID) input(thisTest.getContract(), thisTest.getPaper()) output(thisTest.getContract()) { thisTest.getPaper() } command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) } From d0a7ce3a4ec159cad669aefcfd07e856f22c3cf1 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Tue, 17 Oct 2017 11:58:08 +0100 Subject: [PATCH 08/15] Rename the package to com.r3.corda.enterprise.perftestcordapp --- .../perftestcordapp}/PtCurrencies.kt | 2 +- .../contracts/PtCommercialPaper.kt | 12 +++++----- .../contracts/asset/PtCash.kt | 14 +++++------ .../contracts/asset/PtOnLedgerAsset.kt | 2 +- .../cash/selection/PtCashSelectionH2Impl.kt | 6 ++--- .../flows/AbstractPtCashFlow.kt | 2 +- .../flows/PtCashConfigDataFlow.kt | 10 ++++---- .../perftestcordapp}/flows/PtCashExitFlow.kt | 8 +++---- .../flows/PtCashIssueAndPaymentFlow.kt | 2 +- .../perftestcordapp}/flows/PtCashIssueFlow.kt | 6 ++--- .../flows/PtCashPaymentFlow.kt | 4 ++-- .../flows/TwoPartyTradeFlow.kt | 6 ++--- .../schemas/PtCashSchemaV1.kt | 2 +- .../schemas/PtCommercialPaperSchemaV1.kt | 2 +- .../utils/StateSummingUtilities.kt | 11 +++++---- ...estcordapp.contracts.asset.PtCashSelection | 2 ++ ...da.ptflows.contracts.asset.PtCashSelection | 2 -- .../contracts/PtCommercialPaperTests.kt | 10 ++++---- .../contracts/asset/PtCashTests.kt | 20 ++++++++-------- .../contracts/flows/CashExitFlowTests.kt | 16 ++++++------- .../contracts/flows/CashIssueFlowTests.kt | 12 +++++----- .../contracts/flows/CashPaymentFlowTests.kt | 16 ++++++------- .../contracts/flows/TwoPartyTradeFlowTest.kt | 24 +++++++++---------- 23 files changed, 96 insertions(+), 95 deletions(-) rename perftestflows/src/main/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/PtCurrencies.kt (97%) rename perftestflows/src/main/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/contracts/PtCommercialPaper.kt (95%) rename perftestflows/src/main/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/contracts/asset/PtCash.kt (97%) rename perftestflows/src/main/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/contracts/asset/PtOnLedgerAsset.kt (99%) rename perftestflows/src/main/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/contracts/asset/cash/selection/PtCashSelectionH2Impl.kt (97%) rename perftestflows/src/main/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/flows/AbstractPtCashFlow.kt (96%) rename perftestflows/src/main/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/flows/PtCashConfigDataFlow.kt (82%) rename perftestflows/src/main/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/flows/PtCashExitFlow.kt (93%) rename perftestflows/src/main/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/flows/PtCashIssueAndPaymentFlow.kt (97%) rename perftestflows/src/main/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/flows/PtCashIssueFlow.kt (93%) rename perftestflows/src/main/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/flows/PtCashPaymentFlow.kt (96%) rename perftestflows/src/main/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/flows/TwoPartyTradeFlow.kt (98%) rename perftestflows/src/main/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/schemas/PtCashSchemaV1.kt (96%) rename perftestflows/src/main/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/schemas/PtCommercialPaperSchemaV1.kt (97%) rename perftestflows/src/main/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/utils/StateSummingUtilities.kt (77%) create mode 100644 perftestflows/src/main/resources/META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCashSelection delete mode 100644 perftestflows/src/main/resources/META-INF/services/net.corda.ptflows.contracts.asset.PtCashSelection rename perftestflows/src/test/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/contracts/PtCommercialPaperTests.kt (97%) rename perftestflows/src/test/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/contracts/asset/PtCashTests.kt (97%) rename perftestflows/src/test/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/contracts/flows/CashExitFlowTests.kt (82%) rename perftestflows/src/test/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/contracts/flows/CashIssueFlowTests.kt (84%) rename perftestflows/src/test/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/contracts/flows/CashPaymentFlowTests.kt (89%) rename perftestflows/src/test/kotlin/{net/corda/ptflows => com/r3/corda/enterprise/perftestcordapp}/contracts/flows/TwoPartyTradeFlowTest.kt (97%) diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/PtCurrencies.kt b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/PtCurrencies.kt similarity index 97% rename from perftestflows/src/main/kotlin/net/corda/ptflows/PtCurrencies.kt rename to perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/PtCurrencies.kt index c957b5ce16..ffef67399c 100644 --- a/perftestflows/src/main/kotlin/net/corda/ptflows/PtCurrencies.kt +++ b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/PtCurrencies.kt @@ -1,6 +1,6 @@ @file:JvmName("PtCurrencies") -package net.corda.ptflows +package com.r3.corda.enterprise.perftestcordapp import net.corda.core.contracts.Amount import net.corda.core.contracts.Issued diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/PtCommercialPaper.kt b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaper.kt similarity index 95% rename from perftestflows/src/main/kotlin/net/corda/ptflows/contracts/PtCommercialPaper.kt rename to perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaper.kt index cbc5693735..aba7079ba6 100644 --- a/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/PtCommercialPaper.kt +++ b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaper.kt @@ -1,4 +1,4 @@ -package net.corda.ptflows.contracts +package com.r3.corda.enterprise.perftestcordapp.contracts import co.paralleluniverse.fibers.Suspendable @@ -15,9 +15,9 @@ import net.corda.core.schemas.PersistentState import net.corda.core.schemas.QueryableState import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder -import net.corda.ptflows.contracts.asset.PtCash -import net.corda.ptflows.schemas.PtCommercialPaperSchemaV1 -import net.corda.ptflows.utils.sumCashBy +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash +import com.r3.corda.enterprise.perftestcordapp.schemas.PtCommercialPaperSchemaV1 +import com.r3.corda.enterprise.perftestcordapp.utils.sumCashBy import java.time.Instant import java.util.* @@ -43,12 +43,12 @@ import java.util.* * to do this in the Apache BVal project). */ -val CP_PROGRAM_ID = "net.corda.ptflows.contracts.PtCommercialPaper" +val CP_PROGRAM_ID = "com.r3.corda.enterprise.perftestcordapp.contracts.PtCommercialPaper" // TODO: Generalise the notion of an owned instrument into a superclass/supercontract. Consider composition vs inheritance. class PtCommercialPaper : Contract { companion object { - const val CP_PROGRAM_ID: ContractClassName = "net.corda.ptflows.contracts.PtCommercialPaper" + const val CP_PROGRAM_ID: ContractClassName = "com.r3.corda.enterprise.perftestcordapp.contracts.PtCommercialPaper" } data class State( val issuance: PartyAndReference, diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/PtCash.kt b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCash.kt similarity index 97% rename from perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/PtCash.kt rename to perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCash.kt index 45a5e3adc4..715c454958 100644 --- a/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/PtCash.kt +++ b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCash.kt @@ -1,5 +1,5 @@ // So the static extension functions get put into a class with a better name than CashKt -package net.corda.ptflows.contracts.asset +package com.r3.corda.enterprise.perftestcordapp.contracts.asset import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.* @@ -19,10 +19,10 @@ import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.toBase58String -import net.corda.ptflows.schemas.PtCashSchemaV1 -import net.corda.ptflows.utils.sumCash -import net.corda.ptflows.utils.sumCashOrNull -import net.corda.ptflows.utils.sumCashOrZero +import com.r3.corda.enterprise.perftestcordapp.schemas.PtCashSchemaV1 +import com.r3.corda.enterprise.perftestcordapp.utils.sumCash +import com.r3.corda.enterprise.perftestcordapp.utils.sumCashOrNull +import com.r3.corda.enterprise.perftestcordapp.utils.sumCashOrZero import java.math.BigInteger import java.security.PublicKey import java.sql.DatabaseMetaData @@ -53,7 +53,7 @@ interface PtCashSelection { instance.set(cashSelectionAlgo) cashSelectionAlgo } ?: throw ClassNotFoundException("\nUnable to load compatible cash selection algorithm implementation for JDBC driver ($_metadata)." + - "\nPlease specify an implementation in META-INF/services/net.corda.ptflows.contracts.asset.PtCashSelection") + "\nPlease specify an implementation in META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCashSelection") }.invoke() } } @@ -261,7 +261,7 @@ class PtCash : PtOnLedgerAsset() { } companion object { - const val PROGRAM_ID: ContractClassName = "net.corda.ptflows.contracts.asset.PtCash" + const val PROGRAM_ID: ContractClassName = "com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash" /** * Generate a transaction that moves an amount of currency to the given party, and sends any change back to diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/PtOnLedgerAsset.kt b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtOnLedgerAsset.kt similarity index 99% rename from perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/PtOnLedgerAsset.kt rename to perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtOnLedgerAsset.kt index ac0365ff3a..374ce000aa 100644 --- a/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/PtOnLedgerAsset.kt +++ b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtOnLedgerAsset.kt @@ -1,4 +1,4 @@ -package net.corda.ptflows.contracts.asset +package com.r3.corda.enterprise.perftestcordapp.contracts.asset import net.corda.core.contracts.* diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/cash/selection/PtCashSelectionH2Impl.kt b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/PtCashSelectionH2Impl.kt similarity index 97% rename from perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/cash/selection/PtCashSelectionH2Impl.kt rename to perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/PtCashSelectionH2Impl.kt index fbc8993b26..36e12f7a87 100644 --- a/perftestflows/src/main/kotlin/net/corda/ptflows/contracts/asset/cash/selection/PtCashSelectionH2Impl.kt +++ b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/PtCashSelectionH2Impl.kt @@ -1,8 +1,10 @@ -package net.corda.ptflows.contracts.asset.cash.selection +package com.r3.corda.enterprise.perftestcordapp.contracts.asset.cash.selection import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.strands.Strand +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCashSelection import net.corda.core.contracts.Amount import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef @@ -15,8 +17,6 @@ import net.corda.core.node.services.StatesNotAvailableException import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.deserialize import net.corda.core.utilities.* -import net.corda.ptflows.contracts.asset.PtCash -import net.corda.ptflows.contracts.asset.PtCashSelection import java.sql.DatabaseMetaData import java.sql.SQLException import java.util.* diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/AbstractPtCashFlow.kt b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/AbstractPtCashFlow.kt similarity index 96% rename from perftestflows/src/main/kotlin/net/corda/ptflows/flows/AbstractPtCashFlow.kt rename to perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/AbstractPtCashFlow.kt index 31e256f721..09c2af2dad 100644 --- a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/AbstractPtCashFlow.kt +++ b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/AbstractPtCashFlow.kt @@ -1,4 +1,4 @@ -package net.corda.ptflows.flows +package com.r3.corda.enterprise.perftestcordapp.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.Amount diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashConfigDataFlow.kt b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashConfigDataFlow.kt similarity index 82% rename from perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashConfigDataFlow.kt rename to perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashConfigDataFlow.kt index a235ff6a51..14dc78df3c 100644 --- a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashConfigDataFlow.kt +++ b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashConfigDataFlow.kt @@ -1,14 +1,14 @@ -package net.corda.ptflows.flows +package com.r3.corda.enterprise.perftestcordapp.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.flows.FlowException import net.corda.core.flows.FlowLogic import net.corda.core.flows.StartableByRPC import net.corda.core.serialization.CordaSerializable -import net.corda.ptflows.CHF -import net.corda.ptflows.EUR -import net.corda.ptflows.GBP -import net.corda.ptflows.USD +import com.r3.corda.enterprise.perftestcordapp.CHF +import com.r3.corda.enterprise.perftestcordapp.EUR +import com.r3.corda.enterprise.perftestcordapp.GBP +import com.r3.corda.enterprise.perftestcordapp.USD import java.util.* /** diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashExitFlow.kt b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashExitFlow.kt similarity index 93% rename from perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashExitFlow.kt rename to perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashExitFlow.kt index 2aa360137f..a4384ceabe 100644 --- a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashExitFlow.kt +++ b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashExitFlow.kt @@ -1,4 +1,4 @@ -package net.corda.ptflows.flows +package com.r3.corda.enterprise.perftestcordapp.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.Amount @@ -13,9 +13,9 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.ProgressTracker -import net.corda.ptflows.contracts.asset.PtCash -import net.corda.ptflows.contracts.asset.PtCashSelection -import net.corda.ptflows.issuedBy +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCashSelection +import com.r3.corda.enterprise.perftestcordapp.issuedBy import java.util.* /** diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashIssueAndPaymentFlow.kt b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueAndPaymentFlow.kt similarity index 97% rename from perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashIssueAndPaymentFlow.kt rename to perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueAndPaymentFlow.kt index 74cca5b215..bbcdc54e30 100644 --- a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashIssueAndPaymentFlow.kt +++ b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueAndPaymentFlow.kt @@ -1,4 +1,4 @@ -package net.corda.ptflows.flows +package com.r3.corda.enterprise.perftestcordapp.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.Amount diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashIssueFlow.kt b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueFlow.kt similarity index 93% rename from perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashIssueFlow.kt rename to perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueFlow.kt index e249f5090e..ae63863c69 100644 --- a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashIssueFlow.kt +++ b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueFlow.kt @@ -1,4 +1,4 @@ -package net.corda.ptflows.flows +package com.r3.corda.enterprise.perftestcordapp.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.Amount @@ -8,8 +8,8 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.ProgressTracker -import net.corda.ptflows.contracts.asset.PtCash -import net.corda.ptflows.issuedBy +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash +import com.r3.corda.enterprise.perftestcordapp.issuedBy import java.util.* /** diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashPaymentFlow.kt b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashPaymentFlow.kt similarity index 96% rename from perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashPaymentFlow.kt rename to perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashPaymentFlow.kt index bb92fea5ce..1a29582f69 100644 --- a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/PtCashPaymentFlow.kt +++ b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashPaymentFlow.kt @@ -1,4 +1,4 @@ -package net.corda.ptflows.flows +package com.r3.corda.enterprise.perftestcordapp.flows import co.paralleluniverse.fibers.Suspendable import net.corda.confidential.SwapIdentitiesFlow @@ -10,7 +10,7 @@ import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker -import net.corda.ptflows.contracts.asset.PtCash +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash import java.util.* /** diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/TwoPartyTradeFlow.kt b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlow.kt similarity index 98% rename from perftestflows/src/main/kotlin/net/corda/ptflows/flows/TwoPartyTradeFlow.kt rename to perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlow.kt index 0f40dda661..54d0a0e7f4 100644 --- a/perftestflows/src/main/kotlin/net/corda/ptflows/flows/TwoPartyTradeFlow.kt +++ b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlow.kt @@ -1,4 +1,4 @@ -package net.corda.ptflows.flows +package com.r3.corda.enterprise.perftestcordapp.flows import co.paralleluniverse.fibers.Suspendable import net.corda.confidential.IdentitySyncFlow @@ -12,8 +12,8 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.seconds import net.corda.core.utilities.unwrap -import net.corda.ptflows.contracts.asset.PtCash -import net.corda.ptflows.utils.sumCashBy +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash +import com.r3.corda.enterprise.perftestcordapp.utils.sumCashBy import java.security.PublicKey import java.util.* diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCashSchemaV1.kt b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCashSchemaV1.kt similarity index 96% rename from perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCashSchemaV1.kt rename to perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCashSchemaV1.kt index ca43d2f748..8adbc28a28 100644 --- a/perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCashSchemaV1.kt +++ b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCashSchemaV1.kt @@ -1,4 +1,4 @@ -package net.corda.ptflows.schemas +package com.r3.corda.enterprise.perftestcordapp.schemas import net.corda.core.identity.AbstractParty diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCommercialPaperSchemaV1.kt b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCommercialPaperSchemaV1.kt similarity index 97% rename from perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCommercialPaperSchemaV1.kt rename to perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCommercialPaperSchemaV1.kt index 54c12d99e1..9609eb0560 100644 --- a/perftestflows/src/main/kotlin/net/corda/ptflows/schemas/PtCommercialPaperSchemaV1.kt +++ b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCommercialPaperSchemaV1.kt @@ -1,4 +1,4 @@ -package net.corda.ptflows.schemas +package com.r3.corda.enterprise.perftestcordapp.schemas import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.serialization.CordaSerializable diff --git a/perftestflows/src/main/kotlin/net/corda/ptflows/utils/StateSummingUtilities.kt b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/utils/StateSummingUtilities.kt similarity index 77% rename from perftestflows/src/main/kotlin/net/corda/ptflows/utils/StateSummingUtilities.kt rename to perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/utils/StateSummingUtilities.kt index 66ed583626..2a70515997 100644 --- a/perftestflows/src/main/kotlin/net/corda/ptflows/utils/StateSummingUtilities.kt +++ b/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/utils/StateSummingUtilities.kt @@ -1,5 +1,6 @@ -package net.corda.ptflows.utils +package com.r3.corda.enterprise.perftestcordapp.utils +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount.Companion.sumOrNull import net.corda.core.contracts.Amount.Companion.sumOrThrow @@ -15,20 +16,20 @@ import java.util.* * if there are none, or if any of the cash states cannot be added together (i.e. are * different currencies or issuers). */ -fun Iterable.sumCashBy(owner: AbstractParty): Amount> = filterIsInstance().filter { it.owner == owner }.map { it.amount }.sumOrThrow() +fun Iterable.sumCashBy(owner: AbstractParty): Amount> = filterIsInstance().filter { it.owner == owner }.map { it.amount }.sumOrThrow() /** * Sums the cash states in the list, throwing an exception if there are none, or if any of the cash * states cannot be added together (i.e. are different currencies or issuers). */ -fun Iterable.sumCash(): Amount> = filterIsInstance().map { it.amount }.sumOrThrow() +fun Iterable.sumCash(): Amount> = filterIsInstance().map { it.amount }.sumOrThrow() /** Sums the cash states in the list, returning null if there are none. */ -fun Iterable.sumCashOrNull(): Amount>? = filterIsInstance().map { it.amount }.sumOrNull() +fun Iterable.sumCashOrNull(): Amount>? = filterIsInstance().map { it.amount }.sumOrNull() /** Sums the cash states in the list, returning zero of the given currency+issuer if there are none. */ fun Iterable.sumCashOrZero(currency: Issued): Amount> { - return filterIsInstance().map { it.amount }.sumOrZero(currency) + return filterIsInstance().map { it.amount }.sumOrZero(currency) } /** Sums the asset states in the list, returning null if there are none. */ diff --git a/perftestflows/src/main/resources/META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCashSelection b/perftestflows/src/main/resources/META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCashSelection new file mode 100644 index 0000000000..4785dc0294 --- /dev/null +++ b/perftestflows/src/main/resources/META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCashSelection @@ -0,0 +1,2 @@ +com.r3.corda.enterprise.perftestcordapp.contracts.asset.cash.selection.PtCashSelectionH2Impl + diff --git a/perftestflows/src/main/resources/META-INF/services/net.corda.ptflows.contracts.asset.PtCashSelection b/perftestflows/src/main/resources/META-INF/services/net.corda.ptflows.contracts.asset.PtCashSelection deleted file mode 100644 index c60a063a74..0000000000 --- a/perftestflows/src/main/resources/META-INF/services/net.corda.ptflows.contracts.asset.PtCashSelection +++ /dev/null @@ -1,2 +0,0 @@ -net.corda.ptflows.contracts.asset.cash.selection.PtCashSelectionH2Impl - diff --git a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/PtCommercialPaperTests.kt b/perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaperTests.kt similarity index 97% rename from perftestflows/src/test/kotlin/net/corda/ptflows/contracts/PtCommercialPaperTests.kt rename to perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaperTests.kt index decb9cb494..38c038e9ec 100644 --- a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/PtCommercialPaperTests.kt +++ b/perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaperTests.kt @@ -1,4 +1,4 @@ -package net.corda.ptflows.contracts +package com.r3.corda.enterprise.perftestcordapp.contracts import net.corda.core.contracts.* import net.corda.core.identity.AnonymousParty @@ -9,11 +9,11 @@ import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.days import net.corda.core.utilities.seconds -import net.corda.ptflows.DOLLARS -import net.corda.ptflows.`issued by` -import net.corda.ptflows.contracts.asset.* +import com.r3.corda.enterprise.perftestcordapp.DOLLARS +import com.r3.corda.enterprise.perftestcordapp.`issued by` +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.* import net.corda.testing.* -import net.corda.ptflows.contracts.asset.fillWithSomeTestCash +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.fillWithSomeTestCash import net.corda.testing.node.MockServices import org.junit.Ignore import org.junit.Test diff --git a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/asset/PtCashTests.kt b/perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCashTests.kt similarity index 97% rename from perftestflows/src/test/kotlin/net/corda/ptflows/contracts/asset/PtCashTests.kt rename to perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCashTests.kt index 52e7a3fa3e..20aa08907b 100644 --- a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/asset/PtCashTests.kt +++ b/perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCashTests.kt @@ -1,6 +1,11 @@ -package net.corda.ptflows.contracts.asset +package com.r3.corda.enterprise.perftestcordapp.contracts.asset +import com.r3.corda.enterprise.perftestcordapp.* +import com.r3.corda.enterprise.perftestcordapp.utils.sumCash +import com.r3.corda.enterprise.perftestcordapp.utils.sumCashBy +import com.r3.corda.enterprise.perftestcordapp.utils.sumCashOrNull +import com.r3.corda.enterprise.perftestcordapp.utils.sumCashOrZero import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.crypto.generateKeyPair @@ -15,11 +20,6 @@ import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.OpaqueBytes -import net.corda.ptflows.* -import net.corda.ptflows.utils.sumCash -import net.corda.ptflows.utils.sumCashBy -import net.corda.ptflows.utils.sumCashOrNull -import net.corda.ptflows.utils.sumCashOrZero import net.corda.node.services.vault.NodeVaultService import net.corda.node.utilities.CordaPersistence import net.corda.testing.* @@ -103,8 +103,8 @@ class PtCashTests : TestDependencyInjectionBase() { @Before fun setUp() { LogHelper.setLevel(NodeVaultService::class) - megaCorpServices = MockServices(listOf("net.corda.ptflows.contracts.asset"), MEGA_CORP_KEY) - val databaseAndServices = makeTestDatabaseAndMockServices(cordappPackages = listOf("net.corda.ptflows.contracts.asset"), keys = listOf(MINI_CORP_KEY, MEGA_CORP_KEY, OUR_KEY)) + megaCorpServices = MockServices(listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset"), MEGA_CORP_KEY) + val databaseAndServices = makeTestDatabaseAndMockServices(cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset"), keys = listOf(MINI_CORP_KEY, MEGA_CORP_KEY, OUR_KEY)) database = databaseAndServices.first miniCorpServices = databaseAndServices.second @@ -145,7 +145,7 @@ class PtCashTests : TestDependencyInjectionBase() { output(PtCash.PROGRAM_ID) { outState } command(ALICE_PUBKEY) { DummyCommandData } // Invalid command - this `fails with` "required net.corda.ptflows.contracts.asset.PtCash.Commands.Move command" + this `fails with` "required com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash.Commands.Move command" } tweak { output(PtCash.PROGRAM_ID) { outState } @@ -429,7 +429,7 @@ class PtCashTests : TestDependencyInjectionBase() { tweak { command(MEGA_CORP_PUBKEY) { PtCash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) } - this `fails with` "required net.corda.ptflows.contracts.asset.PtCash.Commands.Move command" + this `fails with` "required com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash.Commands.Move command" tweak { command(MEGA_CORP_PUBKEY) { PtCash.Commands.Move() } diff --git a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashExitFlowTests.kt b/perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashExitFlowTests.kt similarity index 82% rename from perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashExitFlowTests.kt rename to perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashExitFlowTests.kt index 69986b6f44..e65c743615 100644 --- a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashExitFlowTests.kt +++ b/perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashExitFlowTests.kt @@ -1,14 +1,14 @@ -package net.corda.ptflows.contracts.flows +package com.r3.corda.enterprise.perftestcordapp.contracts.flows import net.corda.core.identity.Party import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow -import net.corda.ptflows.flows.PtCashException -import net.corda.ptflows.flows.PtCashExitFlow -import net.corda.ptflows.flows.PtCashIssueFlow -import net.corda.ptflows.DOLLARS -import net.corda.ptflows.`issued by` -import net.corda.ptflows.contracts.asset.PtCash +import com.r3.corda.enterprise.perftestcordapp.flows.PtCashException +import com.r3.corda.enterprise.perftestcordapp.flows.PtCashExitFlow +import com.r3.corda.enterprise.perftestcordapp.flows.PtCashIssueFlow +import com.r3.corda.enterprise.perftestcordapp.DOLLARS +import com.r3.corda.enterprise.perftestcordapp.`issued by` +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash import net.corda.node.internal.StartedNode import net.corda.testing.chooseIdentity import net.corda.testing.getDefaultNotary @@ -34,7 +34,7 @@ class CashExitFlowTests { @Before fun start() { - setCordappPackages("net.corda.ptflows.contracts.asset") + setCordappPackages("com.r3.corda.enterprise.perftestcordapp.contracts.asset") mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin()) val nodes = mockNet.createSomeNodes(1) notaryNode = nodes.notaryNode diff --git a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashIssueFlowTests.kt b/perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashIssueFlowTests.kt similarity index 84% rename from perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashIssueFlowTests.kt rename to perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashIssueFlowTests.kt index 318b1cc53b..d355d264ff 100644 --- a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashIssueFlowTests.kt +++ b/perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashIssueFlowTests.kt @@ -1,13 +1,13 @@ -package net.corda.ptflows.contracts.flows +package com.r3.corda.enterprise.perftestcordapp.contracts.flows import net.corda.core.identity.Party import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow -import net.corda.ptflows.DOLLARS -import net.corda.ptflows.`issued by` -import net.corda.ptflows.contracts.asset.PtCash +import com.r3.corda.enterprise.perftestcordapp.DOLLARS +import com.r3.corda.enterprise.perftestcordapp.`issued by` +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash import net.corda.node.internal.StartedNode -import net.corda.ptflows.flows.PtCashIssueFlow +import com.r3.corda.enterprise.perftestcordapp.flows.PtCashIssueFlow import net.corda.testing.chooseIdentity import net.corda.testing.getDefaultNotary import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin @@ -29,7 +29,7 @@ class CashIssueFlowTests { @Before fun start() { - setCordappPackages("net.corda.ptflows.contracts.asset") + setCordappPackages("com.r3.corda.enterprise.perftestcordapp.contracts.asset") mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin()) val nodes = mockNet.createSomeNodes(1) notaryNode = nodes.notaryNode diff --git a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashPaymentFlowTests.kt b/perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashPaymentFlowTests.kt similarity index 89% rename from perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashPaymentFlowTests.kt rename to perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashPaymentFlowTests.kt index 7a2ee8eaf0..5c1372d02c 100644 --- a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/CashPaymentFlowTests.kt +++ b/perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashPaymentFlowTests.kt @@ -1,4 +1,4 @@ -package net.corda.ptflows.contracts.flows +package com.r3.corda.enterprise.perftestcordapp.contracts.flows import net.corda.core.identity.Party import net.corda.core.node.services.Vault @@ -6,12 +6,12 @@ import net.corda.core.node.services.trackBy import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow -import net.corda.ptflows.DOLLARS -import net.corda.ptflows.`issued by` -import net.corda.ptflows.contracts.asset.PtCash -import net.corda.ptflows.flows.PtCashException -import net.corda.ptflows.flows.PtCashIssueFlow -import net.corda.ptflows.flows.PtCashPaymentFlow +import com.r3.corda.enterprise.perftestcordapp.DOLLARS +import com.r3.corda.enterprise.perftestcordapp.`issued by` +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash +import com.r3.corda.enterprise.perftestcordapp.flows.PtCashException +import com.r3.corda.enterprise.perftestcordapp.flows.PtCashIssueFlow +import com.r3.corda.enterprise.perftestcordapp.flows.PtCashPaymentFlow import net.corda.node.internal.StartedNode import net.corda.testing.chooseIdentity import net.corda.testing.expect @@ -38,7 +38,7 @@ class CashPaymentFlowTests { @Before fun start() { - setCordappPackages("net.corda.ptflows.contracts.asset") + setCordappPackages("com.r3.corda.enterprise.perftestcordapp.contracts.asset") mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin()) val nodes = mockNet.createSomeNodes(1) notaryNode = nodes.notaryNode diff --git a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/TwoPartyTradeFlowTest.kt b/perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/TwoPartyTradeFlowTest.kt similarity index 97% rename from perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/TwoPartyTradeFlowTest.kt rename to perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/TwoPartyTradeFlowTest.kt index 81f7299dfe..c560f8d576 100644 --- a/perftestflows/src/test/kotlin/net/corda/ptflows/contracts/flows/TwoPartyTradeFlowTest.kt +++ b/perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/TwoPartyTradeFlowTest.kt @@ -1,4 +1,4 @@ -package net.corda.ptflows.contracts.flows +package com.r3.corda.enterprise.perftestcordapp.contracts.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.concurrent.CordaFuture @@ -26,15 +26,15 @@ import net.corda.core.utilities.days import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.toNonEmptySet import net.corda.core.utilities.unwrap -import net.corda.ptflows.DOLLARS -import net.corda.ptflows.`issued by` -import net.corda.ptflows.contracts.PtCommercialPaper -import net.corda.ptflows.contracts.asset.CASH -import net.corda.ptflows.contracts.asset.PtCash -import net.corda.ptflows.contracts.asset.`issued by` -import net.corda.ptflows.contracts.asset.`owned by` -import net.corda.ptflows.flows.TwoPartyTradeFlow.Buyer -import net.corda.ptflows.flows.TwoPartyTradeFlow.Seller +import com.r3.corda.enterprise.perftestcordapp.DOLLARS +import com.r3.corda.enterprise.perftestcordapp.`issued by` +import com.r3.corda.enterprise.perftestcordapp.contracts.PtCommercialPaper +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.CASH +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.`issued by` +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.`owned by` +import com.r3.corda.enterprise.perftestcordapp.flows.TwoPartyTradeFlow.Buyer +import com.r3.corda.enterprise.perftestcordapp.flows.TwoPartyTradeFlow.Seller import net.corda.node.internal.StartedNode import net.corda.node.services.api.WritableTransactionStorage import net.corda.node.services.config.NodeConfiguration @@ -42,7 +42,7 @@ import net.corda.node.services.persistence.DBTransactionStorage import net.corda.node.utilities.CordaPersistence import net.corda.nodeapi.internal.ServiceInfo import net.corda.testing.* -import net.corda.ptflows.contracts.asset.fillWithSomeTestCash +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.fillWithSomeTestCash import net.corda.testing.node.InMemoryMessagingNetwork import net.corda.testing.node.MockNetwork import net.corda.testing.node.pumpReceive @@ -73,7 +73,7 @@ class TwoPartyTradeFlowTests { @Before fun before() { - setCordappPackages("net.corda.ptflows.contracts") + setCordappPackages("com.r3.corda.enterprise.perftestcordapp.contracts") LogHelper.setLevel("platform.trade", "core.contract.TransactionGroup", "recordingmap") } From f25c15b194c1d51920c1d317ebb7241cf29026b3 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Tue, 17 Oct 2017 12:27:08 +0100 Subject: [PATCH 09/15] Rename the module/directory from perftestflows to perftestcordapp and clean up module settings --- .idea/compiler.xml | 4 ++-- {perftestflows => perftestcordapp}/build.gradle | 0 .../com/r3/corda/enterprise/perftestcordapp/PtCurrencies.kt | 0 .../enterprise/perftestcordapp/contracts/PtCommercialPaper.kt | 0 .../enterprise/perftestcordapp/contracts/asset/PtCash.kt | 0 .../perftestcordapp/contracts/asset/PtOnLedgerAsset.kt | 0 .../contracts/asset/cash/selection/PtCashSelectionH2Impl.kt | 0 .../enterprise/perftestcordapp/flows/AbstractPtCashFlow.kt | 0 .../enterprise/perftestcordapp/flows/PtCashConfigDataFlow.kt | 0 .../corda/enterprise/perftestcordapp/flows/PtCashExitFlow.kt | 0 .../perftestcordapp/flows/PtCashIssueAndPaymentFlow.kt | 0 .../corda/enterprise/perftestcordapp/flows/PtCashIssueFlow.kt | 0 .../enterprise/perftestcordapp/flows/PtCashPaymentFlow.kt | 0 .../enterprise/perftestcordapp/flows/TwoPartyTradeFlow.kt | 0 .../enterprise/perftestcordapp/schemas/PtCashSchemaV1.kt | 0 .../perftestcordapp/schemas/PtCommercialPaperSchemaV1.kt | 0 .../enterprise/perftestcordapp/utils/StateSummingUtilities.kt | 0 ...enterprise.perftestcordapp.contracts.asset.PtCashSelection | 0 .../perftestcordapp/contracts/PtCommercialPaperTests.kt | 0 .../enterprise/perftestcordapp/contracts/asset/PtCashTests.kt | 0 .../perftestcordapp/contracts/flows/CashExitFlowTests.kt | 0 .../perftestcordapp/contracts/flows/CashIssueFlowTests.kt | 0 .../perftestcordapp/contracts/flows/CashPaymentFlowTests.kt | 0 .../perftestcordapp/contracts/flows/TwoPartyTradeFlowTest.kt | 0 settings.gradle | 2 +- 25 files changed, 3 insertions(+), 3 deletions(-) rename {perftestflows => perftestcordapp}/build.gradle (100%) rename {perftestflows => perftestcordapp}/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/PtCurrencies.kt (100%) rename {perftestflows => perftestcordapp}/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaper.kt (100%) rename {perftestflows => perftestcordapp}/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCash.kt (100%) rename {perftestflows => perftestcordapp}/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtOnLedgerAsset.kt (100%) rename {perftestflows => perftestcordapp}/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/PtCashSelectionH2Impl.kt (100%) rename {perftestflows => perftestcordapp}/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/AbstractPtCashFlow.kt (100%) rename {perftestflows => perftestcordapp}/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashConfigDataFlow.kt (100%) rename {perftestflows => perftestcordapp}/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashExitFlow.kt (100%) rename {perftestflows => perftestcordapp}/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueAndPaymentFlow.kt (100%) rename {perftestflows => perftestcordapp}/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueFlow.kt (100%) rename {perftestflows => perftestcordapp}/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashPaymentFlow.kt (100%) rename {perftestflows => perftestcordapp}/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlow.kt (100%) rename {perftestflows => perftestcordapp}/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCashSchemaV1.kt (100%) rename {perftestflows => perftestcordapp}/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCommercialPaperSchemaV1.kt (100%) rename {perftestflows => perftestcordapp}/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/utils/StateSummingUtilities.kt (100%) rename {perftestflows => perftestcordapp}/src/main/resources/META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCashSelection (100%) rename {perftestflows => perftestcordapp}/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaperTests.kt (100%) rename {perftestflows => perftestcordapp}/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCashTests.kt (100%) rename {perftestflows => perftestcordapp}/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashExitFlowTests.kt (100%) rename {perftestflows => perftestcordapp}/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashIssueFlowTests.kt (100%) rename {perftestflows => perftestcordapp}/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashPaymentFlowTests.kt (100%) rename {perftestflows => perftestcordapp}/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/TwoPartyTradeFlowTest.kt (100%) diff --git a/.idea/compiler.xml b/.idea/compiler.xml index eb97a54cc4..0b6a68fe91 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -77,8 +77,8 @@ - - + + diff --git a/perftestflows/build.gradle b/perftestcordapp/build.gradle similarity index 100% rename from perftestflows/build.gradle rename to perftestcordapp/build.gradle diff --git a/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/PtCurrencies.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/PtCurrencies.kt similarity index 100% rename from perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/PtCurrencies.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/PtCurrencies.kt diff --git a/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaper.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaper.kt similarity index 100% rename from perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaper.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaper.kt diff --git a/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCash.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCash.kt similarity index 100% rename from perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCash.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCash.kt diff --git a/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtOnLedgerAsset.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtOnLedgerAsset.kt similarity index 100% rename from perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtOnLedgerAsset.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtOnLedgerAsset.kt diff --git a/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/PtCashSelectionH2Impl.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/PtCashSelectionH2Impl.kt similarity index 100% rename from perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/PtCashSelectionH2Impl.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/PtCashSelectionH2Impl.kt diff --git a/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/AbstractPtCashFlow.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/AbstractPtCashFlow.kt similarity index 100% rename from perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/AbstractPtCashFlow.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/AbstractPtCashFlow.kt diff --git a/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashConfigDataFlow.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashConfigDataFlow.kt similarity index 100% rename from perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashConfigDataFlow.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashConfigDataFlow.kt diff --git a/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashExitFlow.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashExitFlow.kt similarity index 100% rename from perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashExitFlow.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashExitFlow.kt diff --git a/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueAndPaymentFlow.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueAndPaymentFlow.kt similarity index 100% rename from perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueAndPaymentFlow.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueAndPaymentFlow.kt diff --git a/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueFlow.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueFlow.kt similarity index 100% rename from perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueFlow.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueFlow.kt diff --git a/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashPaymentFlow.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashPaymentFlow.kt similarity index 100% rename from perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashPaymentFlow.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashPaymentFlow.kt diff --git a/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlow.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlow.kt similarity index 100% rename from perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlow.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlow.kt diff --git a/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCashSchemaV1.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCashSchemaV1.kt similarity index 100% rename from perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCashSchemaV1.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCashSchemaV1.kt diff --git a/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCommercialPaperSchemaV1.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCommercialPaperSchemaV1.kt similarity index 100% rename from perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCommercialPaperSchemaV1.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCommercialPaperSchemaV1.kt diff --git a/perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/utils/StateSummingUtilities.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/utils/StateSummingUtilities.kt similarity index 100% rename from perftestflows/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/utils/StateSummingUtilities.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/utils/StateSummingUtilities.kt diff --git a/perftestflows/src/main/resources/META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCashSelection b/perftestcordapp/src/main/resources/META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCashSelection similarity index 100% rename from perftestflows/src/main/resources/META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCashSelection rename to perftestcordapp/src/main/resources/META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCashSelection diff --git a/perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaperTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaperTests.kt similarity index 100% rename from perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaperTests.kt rename to perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaperTests.kt diff --git a/perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCashTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCashTests.kt similarity index 100% rename from perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCashTests.kt rename to perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCashTests.kt diff --git a/perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashExitFlowTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashExitFlowTests.kt similarity index 100% rename from perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashExitFlowTests.kt rename to perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashExitFlowTests.kt diff --git a/perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashIssueFlowTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashIssueFlowTests.kt similarity index 100% rename from perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashIssueFlowTests.kt rename to perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashIssueFlowTests.kt diff --git a/perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashPaymentFlowTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashPaymentFlowTests.kt similarity index 100% rename from perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashPaymentFlowTests.kt rename to perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashPaymentFlowTests.kt diff --git a/perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/TwoPartyTradeFlowTest.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/TwoPartyTradeFlowTest.kt similarity index 100% rename from perftestflows/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/TwoPartyTradeFlowTest.kt rename to perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/TwoPartyTradeFlowTest.kt diff --git a/settings.gradle b/settings.gradle index eabb775ac6..02a26d60b5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -46,5 +46,5 @@ include 'doorman' include 'verify-enclave' include 'sgx-jvm/hsm-tool' include 'signing-server' -include 'perftestflows' +include 'perftestcordapp' From 9a145a710a725a5e46da4fa45354b7f8f16888ab Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Tue, 17 Oct 2017 13:48:20 +0100 Subject: [PATCH 10/15] Remove Pt prefix for classes in perftestcordapp --- .../{PtCurrencies.kt => Currencies.kt} | 2 +- ...tCommercialPaper.kt => CommercialPaper.kt} | 18 +- .../contracts/asset/{PtCash.kt => Cash.kt} | 40 +- .../{PtOnLedgerAsset.kt => OnLedgerAsset.kt} | 4 +- ...ectionH2Impl.kt => CashSelectionH2Impl.kt} | 14 +- ...tractPtCashFlow.kt => AbstractCashFlow.kt} | 2 +- ...onfigDataFlow.kt => CashConfigDataFlow.kt} | 2 +- .../{PtCashExitFlow.kt => CashExitFlow.kt} | 16 +- ...mentFlow.kt => CashIssueAndPaymentFlow.kt} | 8 +- .../{PtCashIssueFlow.kt => CashIssueFlow.kt} | 10 +- ...tCashPaymentFlow.kt => CashPaymentFlow.kt} | 10 +- .../flows/TwoPartyTradeFlow.kt | 4 +- .../{PtCashSchemaV1.kt => CashSchemaV1.kt} | 4 +- ...SchemaV1.kt => CommercialPaperSchemaV1.kt} | 4 +- .../utils/StateSummingUtilities.kt | 10 +- ...testcordapp.contracts.asset.CashSelection} | 2 +- ...lPaperTests.kt => CommercialPaperTests.kt} | 64 +-- .../asset/{PtCashTests.kt => CashTests.kt} | 364 +++++++++--------- .../contracts/flows/CashExitFlowTests.kt | 14 +- .../contracts/flows/CashIssueFlowTests.kt | 10 +- .../contracts/flows/CashPaymentFlowTests.kt | 18 +- .../contracts/flows/TwoPartyTradeFlowTest.kt | 30 +- 22 files changed, 325 insertions(+), 325 deletions(-) rename perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/{PtCurrencies.kt => Currencies.kt} (98%) rename perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/{PtCommercialPaper.kt => CommercialPaper.kt} (94%) rename perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/{PtCash.kt => Cash.kt} (94%) rename perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/{PtOnLedgerAsset.kt => OnLedgerAsset.kt} (99%) rename perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/{PtCashSelectionH2Impl.kt => CashSelectionH2Impl.kt} (94%) rename perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/{AbstractPtCashFlow.kt => AbstractCashFlow.kt} (94%) rename perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/{PtCashConfigDataFlow.kt => CashConfigDataFlow.kt} (95%) rename perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/{PtCashExitFlow.kt => CashExitFlow.kt} (85%) rename perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/{PtCashIssueAndPaymentFlow.kt => CashIssueAndPaymentFlow.kt} (88%) rename perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/{PtCashIssueFlow.kt => CashIssueFlow.kt} (87%) rename perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/{PtCashPaymentFlow.kt => CashPaymentFlow.kt} (92%) rename perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/{PtCashSchemaV1.kt => CashSchemaV1.kt} (89%) rename perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/{PtCommercialPaperSchemaV1.kt => CommercialPaperSchemaV1.kt} (89%) rename perftestcordapp/src/main/resources/META-INF/services/{com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCashSelection => com.r3.corda.enterprise.perftestcordapp.contracts.asset.CashSelection} (69%) rename perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/{PtCommercialPaperTests.kt => CommercialPaperTests.kt} (81%) rename perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/{PtCashTests.kt => CashTests.kt} (66%) diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/PtCurrencies.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/Currencies.kt similarity index 98% rename from perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/PtCurrencies.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/Currencies.kt index ffef67399c..29c8d1a5ed 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/PtCurrencies.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/Currencies.kt @@ -1,4 +1,4 @@ -@file:JvmName("PtCurrencies") +@file:JvmName("Currencies") package com.r3.corda.enterprise.perftestcordapp diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaper.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaper.kt similarity index 94% rename from perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaper.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaper.kt index aba7079ba6..c8ec2eb1fd 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaper.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaper.kt @@ -15,8 +15,8 @@ import net.corda.core.schemas.PersistentState import net.corda.core.schemas.QueryableState import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash -import com.r3.corda.enterprise.perftestcordapp.schemas.PtCommercialPaperSchemaV1 +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash +import com.r3.corda.enterprise.perftestcordapp.schemas.CommercialPaperSchemaV1 import com.r3.corda.enterprise.perftestcordapp.utils.sumCashBy import java.time.Instant import java.util.* @@ -43,12 +43,12 @@ import java.util.* * to do this in the Apache BVal project). */ -val CP_PROGRAM_ID = "com.r3.corda.enterprise.perftestcordapp.contracts.PtCommercialPaper" +val CP_PROGRAM_ID = "com.r3.corda.enterprise.perftestcordapp.contracts.CommercialPaper" // TODO: Generalise the notion of an owned instrument into a superclass/supercontract. Consider composition vs inheritance. -class PtCommercialPaper : Contract { +class CommercialPaper : Contract { companion object { - const val CP_PROGRAM_ID: ContractClassName = "com.r3.corda.enterprise.perftestcordapp.contracts.PtCommercialPaper" + const val CP_PROGRAM_ID: ContractClassName = "com.r3.corda.enterprise.perftestcordapp.contracts.CommercialPaper" } data class State( val issuance: PartyAndReference, @@ -69,13 +69,13 @@ class PtCommercialPaper : Contract { fun withMaturityDate(newMaturityDate: Instant): State = copy(maturityDate = newMaturityDate) /** Object Relational Mapping support. */ - override fun supportedSchemas(): Iterable = listOf(PtCommercialPaperSchemaV1) + override fun supportedSchemas(): Iterable = listOf(CommercialPaperSchemaV1) /** Additional used schemas would be added here (eg. CommercialPaperV2, ...) */ /** Object Relational Mapping support. */ override fun generateMappedObject(schema: MappedSchema): PersistentState { return when (schema) { - is PtCommercialPaperSchemaV1 -> PtCommercialPaperSchemaV1.PersistentCommercialPaperState( + is CommercialPaperSchemaV1 -> CommercialPaperSchemaV1.PersistentCommercialPaperState( issuanceParty = this.issuance.party.owningKey.toBase58String(), issuanceRef = this.issuance.reference.bytes, owner = this.owner.owningKey.toBase58String(), @@ -108,7 +108,7 @@ class PtCommercialPaper : Contract { // There are two possible things that can be done with this CP. The first is trading it. The second is redeeming // it for cash on or after the maturity date. - val command = tx.commands.requireSingleCommand() + val command = tx.commands.requireSingleCommand() val timeWindow: TimeWindow? = tx.timeWindow // Suppress compiler warning as 'key' is an unused variable when destructuring 'groups'. @@ -190,7 +190,7 @@ class PtCommercialPaper : Contract { @Suspendable fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef, services: ServiceHub, ourIdentity: PartyAndCertificate) { // Add the cash movement using the states in our vault. - PtCash.generateSpend(services, tx, paper.state.data.faceValue.withoutIssuer(), ourIdentity, paper.state.data.owner) + Cash.generateSpend(services, tx, paper.state.data.faceValue.withoutIssuer(), ourIdentity, paper.state.data.owner) tx.addInputState(paper) tx.addCommand(Commands.Redeem(), paper.state.data.owner.owningKey) } diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCash.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/Cash.kt similarity index 94% rename from perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCash.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/Cash.kt index 715c454958..a17ce3884a 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCash.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/Cash.kt @@ -19,7 +19,7 @@ import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.toBase58String -import com.r3.corda.enterprise.perftestcordapp.schemas.PtCashSchemaV1 +import com.r3.corda.enterprise.perftestcordapp.schemas.CashSchemaV1 import com.r3.corda.enterprise.perftestcordapp.utils.sumCash import com.r3.corda.enterprise.perftestcordapp.utils.sumCashOrNull import com.r3.corda.enterprise.perftestcordapp.utils.sumCashOrZero @@ -40,20 +40,20 @@ import java.util.concurrent.atomic.AtomicReference * Custom implementations must implement this interface and declare their implementation in * META-INF/services/net.corda.contracts.asset.CashSelection */ -interface PtCashSelection { +interface CashSelection { companion object { - val instance = AtomicReference() + val instance = AtomicReference() - fun getInstance(metadata: () -> java.sql.DatabaseMetaData): PtCashSelection { + fun getInstance(metadata: () -> java.sql.DatabaseMetaData): CashSelection { return instance.get() ?: { val _metadata = metadata() - val cashSelectionAlgos = ServiceLoader.load(PtCashSelection::class.java).toList() + val cashSelectionAlgos = ServiceLoader.load(CashSelection::class.java).toList() val cashSelectionAlgo = cashSelectionAlgos.firstOrNull { it.isCompatible(_metadata) } cashSelectionAlgo?.let { instance.set(cashSelectionAlgo) cashSelectionAlgo } ?: throw ClassNotFoundException("\nUnable to load compatible cash selection algorithm implementation for JDBC driver ($_metadata)." + - "\nPlease specify an implementation in META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCashSelection") + "\nPlease specify an implementation in META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.CashSelection") }.invoke() } } @@ -86,7 +86,7 @@ interface PtCashSelection { onlyFromIssuerParties: Set = emptySet(), notary: Party? = null, lockId: UUID, - withIssuerRefs: Set = emptySet()): List> + withIssuerRefs: Set = emptySet()): List> } /** @@ -102,7 +102,7 @@ interface PtCashSelection { * At the same time, other contracts that just want money and don't care much who is currently holding it in their * vaults can ignore the issuer/depositRefs and just examine the amount fields. */ -class PtCash : PtOnLedgerAsset() { +class Cash : OnLedgerAsset() { override fun extractCommands(commands: Collection>): List> = commands.select() @@ -134,7 +134,7 @@ class PtCash : PtOnLedgerAsset() { /** Object Relational Mapping support. */ override fun generateMappedObject(schema: MappedSchema): PersistentState { return when (schema) { - is PtCashSchemaV1 -> PtCashSchemaV1.PersistentCashState( + is CashSchemaV1 -> CashSchemaV1.PersistentCashState( owner = this.owner, pennies = this.amount.quantity, currency = this.amount.token.product.currencyCode, @@ -147,7 +147,7 @@ class PtCash : PtOnLedgerAsset() { } /** Object Relational Mapping support. */ - override fun supportedSchemas(): Iterable = listOf(PtCashSchemaV1) + override fun supportedSchemas(): Iterable = listOf(CashSchemaV1) /** Additional used schemas would be added here (eg. CashSchemaV2, CashSchemaV3, ...) */ } // DOCEND 1 @@ -261,7 +261,7 @@ class PtCash : PtOnLedgerAsset() { } companion object { - const val PROGRAM_ID: ContractClassName = "com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash" + const val PROGRAM_ID: ContractClassName = "com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash" /** * Generate a transaction that moves an amount of currency to the given party, and sends any change back to @@ -381,27 +381,27 @@ class PtCash : PtOnLedgerAsset() { // Retrieve unspent and unlocked cash states that meet our spending criteria. val totalAmount = payments.map { it.amount }.sumOrThrow() - val cashSelection = PtCashSelection.getInstance({ services.jdbcSession().metaData }) + val cashSelection = CashSelection.getInstance({ services.jdbcSession().metaData }) val acceptableCoins = cashSelection.unconsumedCashStatesForSpending(services, totalAmount, onlyFromParties, tx.notary, tx.lockId) val revocationEnabled = false // Revocation is currently unsupported // Generate a new identity that change will be sent to for confidentiality purposes. This means that a // third party with a copy of the transaction (such as the notary) cannot identify who the change was // sent to val changeIdentity = services.keyManagementService.freshKeyAndCert(ourIdentity, revocationEnabled) - return PtOnLedgerAsset.generateSpend(tx, payments, acceptableCoins, + return OnLedgerAsset.generateSpend(tx, payments, acceptableCoins, changeIdentity.party.anonymise(), { state, quantity, owner -> deriveState(state, quantity, owner) }, - { PtCash().generateMoveCommand() }) + { Cash().generateMoveCommand() }) } } } // Small DSL extensions. -/** @suppress */ infix fun PtCash.State.`owned by`(owner: AbstractParty) = ownedBy(owner) -/** @suppress */ infix fun PtCash.State.`issued by`(party: AbstractParty) = issuedBy(party) -/** @suppress */ infix fun PtCash.State.`issued by`(deposit: PartyAndReference) = issuedBy(deposit) -/** @suppress */ infix fun PtCash.State.`with deposit`(deposit: PartyAndReference): PtCash.State = withDeposit(deposit) +/** @suppress */ infix fun Cash.State.`owned by`(owner: AbstractParty) = ownedBy(owner) +/** @suppress */ infix fun Cash.State.`issued by`(party: AbstractParty) = issuedBy(party) +/** @suppress */ infix fun Cash.State.`issued by`(deposit: PartyAndReference) = issuedBy(deposit) +/** @suppress */ infix fun Cash.State.`with deposit`(deposit: PartyAndReference): Cash.State = withDeposit(deposit) // Unit testing helpers. These could go in a separate file but it's hardly worth it for just a few functions. @@ -410,6 +410,6 @@ val DUMMY_CASH_ISSUER_KEY by lazy { entropyToKeyPair(BigInteger.valueOf(10)) } /** A dummy, randomly generated issuer party by the name of "Snake Oil Issuer" */ val DUMMY_CASH_ISSUER by lazy { Party(CordaX500Name(organisation = "Snake Oil Issuer", locality = "London", country = "GB"), DUMMY_CASH_ISSUER_KEY.public).ref(1) } /** An extension property that lets you write 100.DOLLARS.CASH */ -val Amount.CASH: PtCash.State get() = PtCash.State(Amount(quantity, Issued(DUMMY_CASH_ISSUER, token)), NULL_PARTY) +val Amount.CASH: Cash.State get() = Cash.State(Amount(quantity, Issued(DUMMY_CASH_ISSUER, token)), NULL_PARTY) /** An extension property that lets you get a cash state from an issued token, under the [NULL_PARTY] */ -val Amount>.STATE: PtCash.State get() = PtCash.State(this, NULL_PARTY) +val Amount>.STATE: Cash.State get() = Cash.State(this, NULL_PARTY) diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtOnLedgerAsset.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/OnLedgerAsset.kt similarity index 99% rename from perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtOnLedgerAsset.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/OnLedgerAsset.kt index 374ce000aa..36f92469ed 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtOnLedgerAsset.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/OnLedgerAsset.kt @@ -31,9 +31,9 @@ data class PartyAndAmount(val party: AbstractParty, val amount: Amount< * At the same time, other contracts that just want assets and don't care much who is currently holding it can ignore * the issuer/depositRefs and just examine the amount fields. */ -abstract class PtOnLedgerAsset> : Contract { +abstract class OnLedgerAsset> : Contract { companion object { - val log = loggerFor>() + val log = loggerFor>() /** * Generate a transaction that moves an amount of currency to the given pubkey. diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/PtCashSelectionH2Impl.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/CashSelectionH2Impl.kt similarity index 94% rename from perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/PtCashSelectionH2Impl.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/CashSelectionH2Impl.kt index 36e12f7a87..2bee35ba8a 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/PtCashSelectionH2Impl.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/CashSelectionH2Impl.kt @@ -3,8 +3,8 @@ package com.r3.corda.enterprise.perftestcordapp.contracts.asset.cash.selection import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.strands.Strand -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCashSelection +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.CashSelection import net.corda.core.contracts.Amount import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef @@ -23,11 +23,11 @@ import java.util.* import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock -class PtCashSelectionH2Impl : PtCashSelection { +class CashSelectionH2Impl : CashSelection { companion object { const val JDBC_DRIVER_NAME = "H2 JDBC Driver" - val log = loggerFor() + val log = loggerFor() } override fun isCompatible(metadata: DatabaseMetaData): Boolean { @@ -59,12 +59,12 @@ class PtCashSelectionH2Impl : PtCashSelection { onlyFromIssuerParties: Set, notary: Party?, lockId: UUID, - withIssuerRefs: Set): List> { + withIssuerRefs: Set): List> { val issuerKeysStr = onlyFromIssuerParties.fold("") { left, right -> left + "('${right.owningKey.toBase58String()}')," }.dropLast(1) val issuerRefsStr = withIssuerRefs.fold("") { left, right -> left + "('${right.bytes.toHexString()}')," }.dropLast(1) - val stateAndRefs = mutableListOf>() + val stateAndRefs = mutableListOf>() // We are using an H2 specific means of selecting a minimum set of rows that match a request amount of coins: // 1) There is no standard SQL mechanism of calculating a cumulative total on a field and restricting row selection on the @@ -106,7 +106,7 @@ class PtCashSelectionH2Impl : PtCashSelection { val txHash = SecureHash.parse(rs.getString(1)) val index = rs.getInt(2) val stateRef = StateRef(txHash, index) - val state = rs.getBytes(3).deserialize>(context = SerializationDefaults.STORAGE_CONTEXT) + val state = rs.getBytes(3).deserialize>(context = SerializationDefaults.STORAGE_CONTEXT) val pennies = rs.getLong(4) totalPennies = rs.getLong(5) val rowLockId = rs.getString(6) diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/AbstractPtCashFlow.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/AbstractCashFlow.kt similarity index 94% rename from perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/AbstractPtCashFlow.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/AbstractCashFlow.kt index 09c2af2dad..d1241e8ed3 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/AbstractPtCashFlow.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/AbstractCashFlow.kt @@ -16,7 +16,7 @@ import java.util.* /** * Initiates a flow that produces an Issue/Move or Exit Cash transaction. */ -abstract class AbstractPtCashFlow(override val progressTracker: ProgressTracker) : FlowLogic() { +abstract class AbstractCashFlow(override val progressTracker: ProgressTracker) : FlowLogic() { companion object { object GENERATING_ID : ProgressTracker.Step("Generating anonymous identities") object GENERATING_TX : ProgressTracker.Step("Generating transaction") diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashConfigDataFlow.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashConfigDataFlow.kt similarity index 95% rename from perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashConfigDataFlow.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashConfigDataFlow.kt index 14dc78df3c..cb4ec6d6a2 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashConfigDataFlow.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashConfigDataFlow.kt @@ -15,7 +15,7 @@ import java.util.* * Flow to obtain cash cordapp app configuration. */ @StartableByRPC -class PtCashConfigDataFlow : FlowLogic() { +class CashConfigDataFlow : FlowLogic() { companion object { private val supportedCurrencies = listOf(USD, GBP, CHF, EUR) } diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashExitFlow.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashExitFlow.kt similarity index 85% rename from perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashExitFlow.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashExitFlow.kt index a4384ceabe..53b8727a7d 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashExitFlow.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashExitFlow.kt @@ -13,8 +13,8 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.ProgressTracker -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCashSelection +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.CashSelection import com.r3.corda.enterprise.perftestcordapp.issuedBy import java.util.* @@ -26,9 +26,9 @@ import java.util.* * issuer. */ @StartableByRPC -class PtCashExitFlow(private val amount: Amount, +class CashExitFlow(private val amount: Amount, private val issuerRef: OpaqueBytes, - progressTracker: ProgressTracker) : AbstractPtCashFlow(progressTracker) { + progressTracker: ProgressTracker) : AbstractCashFlow(progressTracker) { constructor(amount: Amount, issueRef: OpaqueBytes) : this(amount, issueRef, tracker()) constructor(request: ExitRequest) : this(request.amount, request.issueRef, tracker()) @@ -42,15 +42,15 @@ class PtCashExitFlow(private val amount: Amount, */ @Suspendable @Throws(PtCashException::class) - override fun call(): AbstractPtCashFlow.Result { + override fun call(): AbstractCashFlow.Result { progressTracker.currentStep = GENERATING_TX val builder = TransactionBuilder(notary = null) val issuer = ourIdentity.ref(issuerRef) - val exitStates = PtCashSelection + val exitStates = CashSelection .getInstance { serviceHub.jdbcSession().metaData } .unconsumedCashStatesForSpending(serviceHub, amount, setOf(issuer.party), builder.notary, builder.lockId, setOf(issuer.reference)) val signers = try { - PtCash().generateExit( + Cash().generateExit( builder, amount.issuedBy(issuer), exitStates) @@ -59,7 +59,7 @@ class PtCashExitFlow(private val amount: Amount, } // Work out who the owners of the burnt states were (specify page size so we don't silently drop any if > DEFAULT_PAGE_SIZE) - val inputStates = serviceHub.vaultQueryService.queryBy(VaultQueryCriteria(stateRefs = builder.inputStates()), + val inputStates = serviceHub.vaultQueryService.queryBy(VaultQueryCriteria(stateRefs = builder.inputStates()), PageSpecification(pageNumber = DEFAULT_PAGE_NUM, pageSize = builder.inputStates().size)).states // TODO: Is it safe to drop participants we don't know how to contact? Does not knowing how to contact them diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueAndPaymentFlow.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueAndPaymentFlow.kt similarity index 88% rename from perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueAndPaymentFlow.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueAndPaymentFlow.kt index bbcdc54e30..97708a0891 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueAndPaymentFlow.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueAndPaymentFlow.kt @@ -21,12 +21,12 @@ import java.util.* * @param notary the notary to set on the output states. */ @StartableByRPC -class PtCashIssueAndPaymentFlow(val amount: Amount, +class CashIssueAndPaymentFlow(val amount: Amount, val issueRef: OpaqueBytes, val recipient: Party, val anonymous: Boolean, val notary: Party, - progressTracker: ProgressTracker) : AbstractPtCashFlow(progressTracker) { + progressTracker: ProgressTracker) : AbstractCashFlow(progressTracker) { constructor(amount: Amount, issueRef: OpaqueBytes, recipient: Party, @@ -36,8 +36,8 @@ class PtCashIssueAndPaymentFlow(val amount: Amount, @Suspendable override fun call(): Result { - subFlow(PtCashIssueFlow(amount, issueRef, notary)) - return subFlow(PtCashPaymentFlow(amount, recipient, anonymous)) + subFlow(CashIssueFlow(amount, issueRef, notary)) + return subFlow(CashPaymentFlow(amount, recipient, anonymous)) } @CordaSerializable diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueFlow.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueFlow.kt similarity index 87% rename from perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueFlow.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueFlow.kt index ae63863c69..d6f71fa1db 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashIssueFlow.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueFlow.kt @@ -8,7 +8,7 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.ProgressTracker -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash import com.r3.corda.enterprise.perftestcordapp.issuedBy import java.util.* @@ -24,21 +24,21 @@ import java.util.* * @param notary the notary to set on the output states. */ @StartableByRPC -class PtCashIssueFlow(private val amount: Amount, +class CashIssueFlow(private val amount: Amount, private val issuerBankPartyRef: OpaqueBytes, private val notary: Party, - progressTracker: ProgressTracker) : AbstractPtCashFlow(progressTracker) { + progressTracker: ProgressTracker) : AbstractCashFlow(progressTracker) { constructor(amount: Amount, issuerBankPartyRef: OpaqueBytes, notary: Party) : this(amount, issuerBankPartyRef, notary, tracker()) constructor(request: IssueRequest) : this(request.amount, request.issueRef, request.notary, tracker()) @Suspendable - override fun call(): AbstractPtCashFlow.Result { + override fun call(): AbstractCashFlow.Result { progressTracker.currentStep = GENERATING_TX val builder = TransactionBuilder(notary) val issuer = ourIdentity.ref(issuerBankPartyRef) - val signers = PtCash().generateIssue(builder, amount.issuedBy(issuer), ourIdentity, notary) + val signers = Cash().generateIssue(builder, amount.issuedBy(issuer), ourIdentity, notary) progressTracker.currentStep = SIGNING_TX val tx = serviceHub.signInitialTransaction(builder, signers) progressTracker.currentStep = FINALISING_TX diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashPaymentFlow.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlow.kt similarity index 92% rename from perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashPaymentFlow.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlow.kt index 1a29582f69..92b19ea5b5 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/PtCashPaymentFlow.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlow.kt @@ -10,7 +10,7 @@ import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash import java.util.* /** @@ -23,12 +23,12 @@ import java.util.* * for testing purposes. */ @StartableByRPC -open class PtCashPaymentFlow( +open class CashPaymentFlow( val amount: Amount, val recipient: Party, val anonymous: Boolean, progressTracker: ProgressTracker, - val issuerConstraint: Set = emptySet()) : AbstractPtCashFlow(progressTracker) { + val issuerConstraint: Set = emptySet()) : AbstractCashFlow(progressTracker) { /** A straightforward constructor that constructs spends using cash states of any issuer. */ constructor(amount: Amount, recipient: Party) : this(amount, recipient, true, tracker()) /** A straightforward constructor that constructs spends using cash states of any issuer. */ @@ -36,7 +36,7 @@ open class PtCashPaymentFlow( constructor(request: PaymentRequest) : this(request.amount, request.recipient, request.anonymous, tracker(), request.issuerConstraint) @Suspendable - override fun call(): AbstractPtCashFlow.Result { + override fun call(): AbstractCashFlow.Result { progressTracker.currentStep = GENERATING_ID val txIdentities = if (anonymous) { subFlow(SwapIdentitiesFlow(recipient)) @@ -48,7 +48,7 @@ open class PtCashPaymentFlow( val builder = TransactionBuilder(notary = null) // TODO: Have some way of restricting this to states the caller controls val (spendTX, keysForSigning) = try { - PtCash.generateSpend(serviceHub, + Cash.generateSpend(serviceHub, builder, amount, ourIdentityAndCert, diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlow.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlow.kt index 54d0a0e7f4..a45ead941c 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlow.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlow.kt @@ -12,7 +12,7 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.seconds import net.corda.core.utilities.unwrap -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash import com.r3.corda.enterprise.perftestcordapp.utils.sumCashBy import java.security.PublicKey import java.util.* @@ -219,7 +219,7 @@ object TwoPartyTradeFlow { val ptx = TransactionBuilder(notary) // Add input and output states for the movement of cash, by using the Cash contract to generate the states - val (tx, cashSigningPubKeys) = PtCash.generateSpend(serviceHub, ptx, tradeRequest.price, ourIdentityAndCert, tradeRequest.payToIdentity.party) + val (tx, cashSigningPubKeys) = Cash.generateSpend(serviceHub, ptx, tradeRequest.price, ourIdentityAndCert, tradeRequest.payToIdentity.party) // Add inputs/outputs/a command for the movement of the asset. tx.addInputState(assetForSale) diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCashSchemaV1.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/CashSchemaV1.kt similarity index 89% rename from perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCashSchemaV1.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/CashSchemaV1.kt index 8adbc28a28..21807a2873 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCashSchemaV1.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/CashSchemaV1.kt @@ -13,14 +13,14 @@ import javax.persistence.Table /** * An object used to fully qualify the [CashSchema] family name (i.e. independent of version). */ -object PtCashSchema +object CashSchema /** * First version of a cash contract ORM schema that maps all fields of the [Cash] contract state as it stood * at the time of writing. */ @CordaSerializable -object PtCashSchemaV1 : MappedSchema(schemaFamily = PtCashSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCashState::class.java)) { +object CashSchemaV1 : MappedSchema(schemaFamily = CashSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCashState::class.java)) { @Entity @Table(name = "contract_cash_states", indexes = arrayOf(Index(name = "ccy_code_idx", columnList = "ccy_code"), diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCommercialPaperSchemaV1.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/CommercialPaperSchemaV1.kt similarity index 89% rename from perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCommercialPaperSchemaV1.kt rename to perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/CommercialPaperSchemaV1.kt index 9609eb0560..f263750c1d 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/PtCommercialPaperSchemaV1.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/CommercialPaperSchemaV1.kt @@ -11,14 +11,14 @@ import javax.persistence.Table /** * An object used to fully qualify the [CommercialPaperSchema] family name (i.e. independent of version). */ -object PtCommercialPaperSchema +object CommercialPaperSchema /** * First version of a commercial paper contract ORM schema that maps all fields of the [CommercialPaper] contract state * as it stood at the time of writing. */ @CordaSerializable -object PtCommercialPaperSchemaV1 : MappedSchema(schemaFamily = PtCommercialPaperSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCommercialPaperState::class.java)) { +object CommercialPaperSchemaV1 : MappedSchema(schemaFamily = CommercialPaperSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCommercialPaperState::class.java)) { @Entity @Table(name = "cp_states", indexes = arrayOf(Index(name = "ccy_code_index", columnList = "ccy_code"), diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/utils/StateSummingUtilities.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/utils/StateSummingUtilities.kt index 2a70515997..9a6457b370 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/utils/StateSummingUtilities.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/utils/StateSummingUtilities.kt @@ -1,6 +1,6 @@ package com.r3.corda.enterprise.perftestcordapp.utils -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount.Companion.sumOrNull import net.corda.core.contracts.Amount.Companion.sumOrThrow @@ -16,20 +16,20 @@ import java.util.* * if there are none, or if any of the cash states cannot be added together (i.e. are * different currencies or issuers). */ -fun Iterable.sumCashBy(owner: AbstractParty): Amount> = filterIsInstance().filter { it.owner == owner }.map { it.amount }.sumOrThrow() +fun Iterable.sumCashBy(owner: AbstractParty): Amount> = filterIsInstance().filter { it.owner == owner }.map { it.amount }.sumOrThrow() /** * Sums the cash states in the list, throwing an exception if there are none, or if any of the cash * states cannot be added together (i.e. are different currencies or issuers). */ -fun Iterable.sumCash(): Amount> = filterIsInstance().map { it.amount }.sumOrThrow() +fun Iterable.sumCash(): Amount> = filterIsInstance().map { it.amount }.sumOrThrow() /** Sums the cash states in the list, returning null if there are none. */ -fun Iterable.sumCashOrNull(): Amount>? = filterIsInstance().map { it.amount }.sumOrNull() +fun Iterable.sumCashOrNull(): Amount>? = filterIsInstance().map { it.amount }.sumOrNull() /** Sums the cash states in the list, returning zero of the given currency+issuer if there are none. */ fun Iterable.sumCashOrZero(currency: Issued): Amount> { - return filterIsInstance().map { it.amount }.sumOrZero(currency) + return filterIsInstance().map { it.amount }.sumOrZero(currency) } /** Sums the asset states in the list, returning null if there are none. */ diff --git a/perftestcordapp/src/main/resources/META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCashSelection b/perftestcordapp/src/main/resources/META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.CashSelection similarity index 69% rename from perftestcordapp/src/main/resources/META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCashSelection rename to perftestcordapp/src/main/resources/META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.CashSelection index 4785dc0294..c9ce8a6ee9 100644 --- a/perftestcordapp/src/main/resources/META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCashSelection +++ b/perftestcordapp/src/main/resources/META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.CashSelection @@ -1,2 +1,2 @@ -com.r3.corda.enterprise.perftestcordapp.contracts.asset.cash.selection.PtCashSelectionH2Impl +com.r3.corda.enterprise.perftestcordapp.contracts.asset.cash.selection.CashSelectionH2Impl diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaperTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt similarity index 81% rename from perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaperTests.kt rename to perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt index 38c038e9ec..21577cd447 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/PtCommercialPaperTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt @@ -26,8 +26,8 @@ import kotlin.test.assertTrue // TODO: The generate functions aren't tested by these tests: add them. -interface PtCommercialPaperTestTemplate { - fun getPaper(): PtCommercialPaper.State +interface CommercialPaperTestTemplate { + fun getPaper(): CommercialPaper.State fun getIssueCommand(notary: Party): CommandData fun getRedeemCommand(notary: Party): CommandData fun getMoveCommand(): CommandData @@ -35,32 +35,32 @@ interface PtCommercialPaperTestTemplate { } -class KotlinCommercialPaperTest : PtCommercialPaperTestTemplate { - override fun getPaper(): PtCommercialPaper.State = PtCommercialPaper.State( +class KotlinCommercialPaperTest : CommercialPaperTestTemplate { + override fun getPaper(): CommercialPaper.State = CommercialPaper.State( issuance = MEGA_CORP.ref(123), owner = MEGA_CORP, faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123), maturityDate = TEST_TX_TIME + 7.days ) - override fun getIssueCommand(notary: Party): CommandData = PtCommercialPaper.Commands.Issue() - override fun getRedeemCommand(notary: Party): CommandData = PtCommercialPaper.Commands.Redeem() - override fun getMoveCommand(): CommandData = PtCommercialPaper.Commands.Move() - override fun getContract() = PtCommercialPaper.CP_PROGRAM_ID + override fun getIssueCommand(notary: Party): CommandData = CommercialPaper.Commands.Issue() + override fun getRedeemCommand(notary: Party): CommandData = CommercialPaper.Commands.Redeem() + override fun getMoveCommand(): CommandData = CommercialPaper.Commands.Move() + override fun getContract() = CommercialPaper.CP_PROGRAM_ID } -class KotlinCommercialPaperLegacyTest : PtCommercialPaperTestTemplate { - override fun getPaper(): PtCommercialPaper.State = PtCommercialPaper.State( +class KotlinCommercialPaperLegacyTest : CommercialPaperTestTemplate { + override fun getPaper(): CommercialPaper.State = CommercialPaper.State( issuance = MEGA_CORP.ref(123), owner = MEGA_CORP, faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123), maturityDate = TEST_TX_TIME + 7.days ) - override fun getIssueCommand(notary: Party): CommandData = PtCommercialPaper.Commands.Issue() - override fun getRedeemCommand(notary: Party): CommandData = PtCommercialPaper.Commands.Redeem() - override fun getMoveCommand(): CommandData = PtCommercialPaper.Commands.Move() - override fun getContract() = PtCommercialPaper.CP_PROGRAM_ID + override fun getIssueCommand(notary: Party): CommandData = CommercialPaper.Commands.Issue() + override fun getRedeemCommand(notary: Party): CommandData = CommercialPaper.Commands.Redeem() + override fun getMoveCommand(): CommandData = CommercialPaper.Commands.Move() + override fun getContract() = CommercialPaper.CP_PROGRAM_ID } @RunWith(Parameterized::class) @@ -71,7 +71,7 @@ class CommercialPaperTestsGeneric { } @Parameterized.Parameter - lateinit var thisTest: PtCommercialPaperTestTemplate + lateinit var thisTest: CommercialPaperTestTemplate val issuer = MEGA_CORP.ref(123) @@ -80,14 +80,14 @@ class CommercialPaperTestsGeneric { val someProfits = 1200.DOLLARS `issued by` issuer ledger { unverifiedTransaction { - attachment(PtCash.PROGRAM_ID) - output(PtCash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE) - output(PtCash.PROGRAM_ID, "some profits", someProfits.STATE `owned by` MEGA_CORP) + 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, PtCommercialPaper.CP_PROGRAM_ID) + attachments(CP_PROGRAM_ID, CommercialPaper.CP_PROGRAM_ID) output(thisTest.getContract(), "paper") { thisTest.getPaper() } command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand(DUMMY_NOTARY) } timeWindow(TEST_TX_TIME) @@ -97,12 +97,12 @@ 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(PtCash.PROGRAM_ID, PtCommercialPaper.CP_PROGRAM_ID) + attachments(Cash.PROGRAM_ID, CommercialPaper.CP_PROGRAM_ID) input("paper") input("alice's $900") - output(PtCash.PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP } - output(thisTest.getContract(), "alice's paper") { "paper".output().withOwner(ALICE) } - command(ALICE_PUBKEY) { PtCash.Commands.Move() } + output(Cash.PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP } + output(thisTest.getContract(), "alice's paper") { "paper".output().withOwner(ALICE) } + command(ALICE_PUBKEY) { Cash.Commands.Move() } command(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() } this.verifies() } @@ -110,16 +110,16 @@ 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, PtCommercialPaper.CP_PROGRAM_ID) + attachments(CP_PROGRAM_ID, CommercialPaper.CP_PROGRAM_ID) input("alice's paper") input("some profits") fun TransactionDSL.outputs(aliceGetsBack: Amount>) { - output(PtCash.PROGRAM_ID, "Alice's profit") { aliceGetsBack.STATE `owned by` ALICE } - output(PtCash.PROGRAM_ID, "Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP } + output(Cash.PROGRAM_ID, "Alice's profit") { aliceGetsBack.STATE `owned by` ALICE } + output(Cash.PROGRAM_ID, "Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP } } - command(MEGA_CORP_PUBKEY) { PtCash.Commands.Move() } + command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() } command(ALICE_PUBKEY) { thisTest.getRedeemCommand(DUMMY_NOTARY) } tweak { @@ -137,7 +137,7 @@ class CommercialPaperTestsGeneric { timeWindow(TEST_TX_TIME + 8.days) tweak { - output(thisTest.getContract()) { "paper".output() } + output(thisTest.getContract()) { "paper".output() } this `fails with` "must be destroyed" } @@ -245,7 +245,7 @@ class CommercialPaperTestsGeneric { // BigCorpâ„¢ issues $10,000 of commercial paper, to mature in 30 days, owned initially by itself. val faceValue = 10000.DOLLARS `issued by` DUMMY_CASH_ISSUER val issuance = bigCorpServices.myInfo.chooseIdentity().ref(1) - val issueBuilder = PtCommercialPaper().generateIssue(issuance, faceValue, TEST_TX_TIME + 30.days, DUMMY_NOTARY) + val issueBuilder = CommercialPaper().generateIssue(issuance, faceValue, TEST_TX_TIME + 30.days, DUMMY_NOTARY) issueBuilder.setTimeWindow(TEST_TX_TIME, 30.seconds) val issuePtx = bigCorpServices.signInitialTransaction(issueBuilder) val issueTx = notaryServices.addSignature(issuePtx) @@ -254,8 +254,8 @@ class CommercialPaperTestsGeneric { // Alice pays $9000 to BigCorp to own some of their debt. moveTX = run { val builder = TransactionBuilder(DUMMY_NOTARY) - PtCash.generateSpend(aliceServices, builder, 9000.DOLLARS, AnonymousParty(bigCorpServices.key.public)) - PtCommercialPaper().generateMove(builder, issueTx.tx.outRef(0), AnonymousParty(aliceServices.key.public)) + Cash.generateSpend(aliceServices, builder, 9000.DOLLARS, AnonymousParty(bigCorpServices.key.public)) + CommercialPaper().generateMove(builder, issueTx.tx.outRef(0), AnonymousParty(aliceServices.key.public)) val ptx = aliceServices.signInitialTransaction(builder) val ptx2 = bigCorpServices.addSignature(ptx) val stx = notaryServices.addSignature(ptx2) @@ -276,7 +276,7 @@ class CommercialPaperTestsGeneric { fun makeRedeemTX(time: Instant): Pair { val builder = TransactionBuilder(DUMMY_NOTARY) builder.setTimeWindow(time, 30.seconds) - PtCommercialPaper().generateRedeem(builder, moveTX.tx.outRef(1), bigCorpServices, bigCorpServices.myInfo.chooseIdentityAndCert()) + CommercialPaper().generateRedeem(builder, moveTX.tx.outRef(1), bigCorpServices, bigCorpServices.myInfo.chooseIdentityAndCert()) val ptx = aliceServices.signInitialTransaction(builder) val ptx2 = bigCorpServices.addSignature(ptx) val stx = notaryServices.addSignature(ptx2) diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCashTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt similarity index 66% rename from perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCashTests.kt rename to perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt index 20aa08907b..c58ff06be3 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/PtCashTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt @@ -53,14 +53,14 @@ fun ServiceHub.fillWithSomeTestCash(howMuch: Amount, rng: Random = Random(), ref: OpaqueBytes = OpaqueBytes(ByteArray(1, { 1 })), ownedBy: AbstractParty? = null, - issuedBy: PartyAndReference = DUMMY_CASH_ISSUER): Vault { + issuedBy: PartyAndReference = DUMMY_CASH_ISSUER): Vault { val amounts = calculateRandomlySizedAmounts(howMuch, atLeastThisManyStates, atMostThisManyStates, rng) val myKey = ownedBy?.owningKey ?: myInfo.chooseIdentity().owningKey val anonParty = AnonymousParty(myKey) // We will allocate one state to one transaction, for simplicities sake. - val cash = PtCash() + val cash = Cash() val transactions: List = amounts.map { pennies -> val issuance = TransactionBuilder(null as Party?) cash.generateIssue(issuance, Amount(pennies, Issued(issuedBy.copy(reference = ref), howMuch.token)), anonParty, outputNotary) @@ -72,17 +72,17 @@ fun ServiceHub.fillWithSomeTestCash(howMuch: Amount, // Get all the StateRefs of all the generated transactions. val states = transactions.flatMap { stx -> - stx.tx.outputs.indices.map { i -> stx.tx.outRef(i) } + stx.tx.outputs.indices.map { i -> stx.tx.outRef(i) } } return Vault(states) } -class PtCashTests : TestDependencyInjectionBase() { +class CashTests : TestDependencyInjectionBase() { val defaultRef = OpaqueBytes(ByteArray(1, { 1 })) val defaultIssuer = MEGA_CORP.ref(defaultRef) - val inState = PtCash.State( + val inState = Cash.State( amount = 1000.DOLLARS `issued by` defaultIssuer, owner = AnonymousParty(ALICE_PUBKEY) ) @@ -90,7 +90,7 @@ class PtCashTests : TestDependencyInjectionBase() { val issuerInState = inState.copy(owner = defaultIssuer.party) val outState = issuerInState.copy(owner = AnonymousParty(BOB_PUBKEY)) - fun PtCash.State.editDepositRef(ref: Byte) = copy( + fun Cash.State.editDepositRef(ref: Byte) = copy( amount = Amount(amount.quantity, token = amount.token.copy(amount.token.issuer.copy(reference = OpaqueBytes.of(ref)))) ) @@ -98,7 +98,7 @@ class PtCashTests : TestDependencyInjectionBase() { lateinit var megaCorpServices: MockServices val vault: VaultService get() = miniCorpServices.vaultService lateinit var database: CordaPersistence - lateinit var vaultStatesUnconsumed: List> + lateinit var vaultStatesUnconsumed: List> @Before fun setUp() { @@ -120,7 +120,7 @@ class PtCashTests : TestDependencyInjectionBase() { ownedBy = OUR_IDENTITY_1, issuedBy = MINI_CORP.ref(1), issuerServices = miniCorpServices) } database.transaction { - vaultStatesUnconsumed = miniCorpServices.vaultQueryService.queryBy().states + vaultStatesUnconsumed = miniCorpServices.vaultQueryService.queryBy().states } resetTestSerialization() } @@ -133,35 +133,35 @@ class PtCashTests : TestDependencyInjectionBase() { @Test fun trivial() { transaction { - attachment(PtCash.PROGRAM_ID) - input(PtCash.PROGRAM_ID) { inState } + attachment(Cash.PROGRAM_ID) + input(Cash.PROGRAM_ID) { inState } tweak { - output(PtCash.PROGRAM_ID) { outState.copy(amount = 2000.DOLLARS `issued by` defaultIssuer) } - command(ALICE_PUBKEY) { PtCash.Commands.Move() } + output(Cash.PROGRAM_ID) { outState.copy(amount = 2000.DOLLARS `issued by` defaultIssuer) } + command(ALICE_PUBKEY) { Cash.Commands.Move() } this `fails with` "the amounts balance" } tweak { - output(PtCash.PROGRAM_ID) { outState } + output(Cash.PROGRAM_ID) { outState } command(ALICE_PUBKEY) { DummyCommandData } // Invalid command - this `fails with` "required com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash.Commands.Move command" + this `fails with` "required com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash.Commands.Move command" } tweak { - output(PtCash.PROGRAM_ID) { outState } - command(BOB_PUBKEY) { PtCash.Commands.Move() } + output(Cash.PROGRAM_ID) { outState } + command(BOB_PUBKEY) { Cash.Commands.Move() } this `fails with` "the owning keys are a subset of the signing keys" } tweak { - output(PtCash.PROGRAM_ID) { outState } - output(PtCash.PROGRAM_ID) { outState `issued by` MINI_CORP } - command(ALICE_PUBKEY) { PtCash.Commands.Move() } + output(Cash.PROGRAM_ID) { outState } + output(Cash.PROGRAM_ID) { outState `issued by` MINI_CORP } + command(ALICE_PUBKEY) { Cash.Commands.Move() } this `fails with` "at least one cash input" } // Simple reallocation works. tweak { - output(PtCash.PROGRAM_ID) { outState } - command(ALICE_PUBKEY) { PtCash.Commands.Move() } + output(Cash.PROGRAM_ID) { outState } + command(ALICE_PUBKEY) { Cash.Commands.Move() } this.verifies() } } @@ -171,10 +171,10 @@ class PtCashTests : TestDependencyInjectionBase() { fun `issue by move`() { // Check we can't "move" money into existence. transaction { - attachment(PtCash.PROGRAM_ID) - input(PtCash.PROGRAM_ID) { DummyState() } - output(PtCash.PROGRAM_ID) { outState } - command(MINI_CORP_PUBKEY) { PtCash.Commands.Move() } + attachment(Cash.PROGRAM_ID) + input(Cash.PROGRAM_ID) { DummyState() } + output(Cash.PROGRAM_ID) { outState } + command(MINI_CORP_PUBKEY) { Cash.Commands.Move() } this `fails with` "there is at least one cash input for this group" } @@ -185,20 +185,20 @@ class PtCashTests : 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(PtCash.PROGRAM_ID) - output(PtCash.PROGRAM_ID) { outState } - command(ALICE_PUBKEY) { PtCash.Commands.Issue() } + 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(PtCash.PROGRAM_ID) - output(PtCash.PROGRAM_ID) { - PtCash.State( + attachment(Cash.PROGRAM_ID) + output(Cash.PROGRAM_ID) { + Cash.State( amount = 1000.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = AnonymousParty(ALICE_PUBKEY) ) } - command(MINI_CORP_PUBKEY) { PtCash.Commands.Issue() } + command(MINI_CORP_PUBKEY) { Cash.Commands.Issue() } this.verifies() } } @@ -208,14 +208,14 @@ class PtCashTests : TestDependencyInjectionBase() { initialiseTestSerialization() // Test generation works. val tx: WireTransaction = TransactionBuilder(notary = null).apply { - PtCash().generateIssue(this, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = AnonymousParty(ALICE_PUBKEY), notary = DUMMY_NOTARY) + Cash().generateIssue(this, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = AnonymousParty(ALICE_PUBKEY), notary = DUMMY_NOTARY) }.toWireTransaction(miniCorpServices) assertTrue(tx.inputs.isEmpty()) - val s = tx.outputsOfType().single() + val s = tx.outputsOfType().single() assertEquals(100.DOLLARS `issued by` MINI_CORP.ref(12, 34), s.amount) assertEquals(MINI_CORP as AbstractParty, s.amount.token.issuer.party) assertEquals(AnonymousParty(ALICE_PUBKEY), s.owner) - assertTrue(tx.commands[0].value is PtCash.Commands.Issue) + assertTrue(tx.commands[0].value is Cash.Commands.Issue) assertEquals(MINI_CORP_PUBKEY, tx.commands[0].signers[0]) } @@ -225,7 +225,7 @@ class PtCashTests : TestDependencyInjectionBase() { // Test issuance from an issued amount val amount = 100.DOLLARS `issued by` MINI_CORP.ref(12, 34) val tx: WireTransaction = TransactionBuilder(notary = null).apply { - PtCash().generateIssue(this, amount, owner = AnonymousParty(ALICE_PUBKEY), notary = DUMMY_NOTARY) + Cash().generateIssue(this, amount, owner = AnonymousParty(ALICE_PUBKEY), notary = DUMMY_NOTARY) }.toWireTransaction(miniCorpServices) assertTrue(tx.inputs.isEmpty()) assertEquals(tx.outputs[0], tx.outputs[0]) @@ -235,49 +235,49 @@ class PtCashTests : 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(PtCash.PROGRAM_ID) - input(PtCash.PROGRAM_ID) { issuerInState } - output(PtCash.PROGRAM_ID) { inState.copy(amount = inState.amount * 2) } + attachment(Cash.PROGRAM_ID) + input(Cash.PROGRAM_ID) { issuerInState } + output(Cash.PROGRAM_ID) { inState.copy(amount = inState.amount * 2) } // Move fails: not allowed to summon money. tweak { - command(ALICE_PUBKEY) { PtCash.Commands.Move() } + command(ALICE_PUBKEY) { Cash.Commands.Move() } this `fails with` "the amounts balance" } // Issue works. tweak { - command(MEGA_CORP_PUBKEY) { PtCash.Commands.Issue() } + command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() } this.verifies() } } // Can't use an issue command to lower the amount. transaction { - attachment(PtCash.PROGRAM_ID) - input(PtCash.PROGRAM_ID) { inState } - output(PtCash.PROGRAM_ID) { inState.copy(amount = inState.amount.splitEvenly(2).first()) } - command(MEGA_CORP_PUBKEY) { PtCash.Commands.Issue() } + 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() } this `fails with` "output values sum to more than the inputs" } // Can't have an issue command that doesn't actually issue money. transaction { - attachment(PtCash.PROGRAM_ID) - input(PtCash.PROGRAM_ID) { inState } - output(PtCash.PROGRAM_ID) { inState } - command(MEGA_CORP_PUBKEY) { PtCash.Commands.Issue() } + attachment(Cash.PROGRAM_ID) + input(Cash.PROGRAM_ID) { inState } + output(Cash.PROGRAM_ID) { inState } + command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() } this `fails with` "output values sum to more than the inputs" } // Can't have any other commands if we have an issue command (because the issue command overrules them) transaction { - attachment(PtCash.PROGRAM_ID) - input(PtCash.PROGRAM_ID) { inState } - output(PtCash.PROGRAM_ID) { inState.copy(amount = inState.amount * 2) } - command(MEGA_CORP_PUBKEY) { PtCash.Commands.Issue() } + 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() } tweak { - command(MEGA_CORP_PUBKEY) { PtCash.Commands.Issue() } + command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() } this `fails with` "there is only a single issue command" } this.verifies() @@ -294,40 +294,40 @@ class PtCashTests : TestDependencyInjectionBase() { // Issue some cash var ptx = TransactionBuilder(DUMMY_NOTARY) - PtCash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP, notary = DUMMY_NOTARY) + Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP, notary = DUMMY_NOTARY) val tx = miniCorpServices.signInitialTransaction(ptx) // Include the previously issued cash in a new issuance command ptx = TransactionBuilder(DUMMY_NOTARY) - ptx.addInputState(tx.tx.outRef(0)) - PtCash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP, notary = DUMMY_NOTARY) + ptx.addInputState(tx.tx.outRef(0)) + Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP, notary = DUMMY_NOTARY) } @Test fun testMergeSplit() { // Splitting value works. transaction { - attachment(PtCash.PROGRAM_ID) - command(ALICE_PUBKEY) { PtCash.Commands.Move() } + attachment(Cash.PROGRAM_ID) + command(ALICE_PUBKEY) { Cash.Commands.Move() } tweak { - input(PtCash.PROGRAM_ID) { inState } + input(Cash.PROGRAM_ID) { inState } val splits4 = inState.amount.splitEvenly(4) - for (i in 0..3) output(PtCash.PROGRAM_ID) { inState.copy(amount = splits4[i]) } + for (i in 0..3) output(Cash.PROGRAM_ID) { inState.copy(amount = splits4[i]) } this.verifies() } // Merging 4 inputs into 2 outputs works. tweak { val splits2 = inState.amount.splitEvenly(2) val splits4 = inState.amount.splitEvenly(4) - for (i in 0..3) input(PtCash.PROGRAM_ID) { inState.copy(amount = splits4[i]) } - for (i in 0..1) output(PtCash.PROGRAM_ID) { inState.copy(amount = splits2[i]) } + for (i in 0..3) input(Cash.PROGRAM_ID) { inState.copy(amount = splits4[i]) } + for (i in 0..1) output(Cash.PROGRAM_ID) { inState.copy(amount = splits2[i]) } this.verifies() } // Merging 2 inputs into 1 works. tweak { val splits2 = inState.amount.splitEvenly(2) - for (i in 0..1) input(PtCash.PROGRAM_ID) { inState.copy(amount = splits2[i]) } - output(PtCash.PROGRAM_ID) { inState } + for (i in 0..1) input(Cash.PROGRAM_ID) { inState.copy(amount = splits2[i]) } + output(Cash.PROGRAM_ID) { inState } this.verifies() } } @@ -336,18 +336,18 @@ class PtCashTests : TestDependencyInjectionBase() { @Test fun zeroSizedValues() { transaction { - attachment(PtCash.PROGRAM_ID) - input(PtCash.PROGRAM_ID) { inState } - input(PtCash.PROGRAM_ID) { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) } - command(ALICE_PUBKEY) { PtCash.Commands.Move() } + 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(PtCash.PROGRAM_ID) - input(PtCash.PROGRAM_ID) { inState } - output(PtCash.PROGRAM_ID) { inState } - output(PtCash.PROGRAM_ID) { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) } - command(ALICE_PUBKEY) { PtCash.Commands.Move() } + 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) } + command(ALICE_PUBKEY) { Cash.Commands.Move() } this `fails with` "zero sized outputs" } } @@ -356,59 +356,59 @@ class PtCashTests : TestDependencyInjectionBase() { fun trivialMismatches() { // Can't change issuer. transaction { - attachment(PtCash.PROGRAM_ID) - input(PtCash.PROGRAM_ID) { inState } - output(PtCash.PROGRAM_ID) { outState `issued by` MINI_CORP } - command(ALICE_PUBKEY) { PtCash.Commands.Move() } + attachment(Cash.PROGRAM_ID) + input(Cash.PROGRAM_ID) { inState } + output(Cash.PROGRAM_ID) { outState `issued by` MINI_CORP } + command(ALICE_PUBKEY) { Cash.Commands.Move() } this `fails with` "the amounts balance" } // Can't change deposit reference when splitting. transaction { - attachment(PtCash.PROGRAM_ID) + attachment(Cash.PROGRAM_ID) val splits2 = inState.amount.splitEvenly(2) - input(PtCash.PROGRAM_ID) { inState } - for (i in 0..1) output(PtCash.PROGRAM_ID) { outState.copy(amount = splits2[i]).editDepositRef(i.toByte()) } - command(ALICE_PUBKEY) { PtCash.Commands.Move() } + input(Cash.PROGRAM_ID) { inState } + for (i in 0..1) output(Cash.PROGRAM_ID) { outState.copy(amount = splits2[i]).editDepositRef(i.toByte()) } + command(ALICE_PUBKEY) { Cash.Commands.Move() } this `fails with` "the amounts balance" } // Can't mix currencies. transaction { - attachment(PtCash.PROGRAM_ID) - input(PtCash.PROGRAM_ID) { inState } - output(PtCash.PROGRAM_ID) { outState.copy(amount = 800.DOLLARS `issued by` defaultIssuer) } - output(PtCash.PROGRAM_ID) { outState.copy(amount = 200.POUNDS `issued by` defaultIssuer) } - command(ALICE_PUBKEY) { PtCash.Commands.Move() } + 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) } + command(ALICE_PUBKEY) { Cash.Commands.Move() } this `fails with` "the amounts balance" } transaction { - attachment(PtCash.PROGRAM_ID) - input(PtCash.PROGRAM_ID) { inState } - input(PtCash.PROGRAM_ID) { + attachment(Cash.PROGRAM_ID) + input(Cash.PROGRAM_ID) { inState } + input(Cash.PROGRAM_ID) { inState.copy( amount = 150.POUNDS `issued by` defaultIssuer, owner = AnonymousParty(BOB_PUBKEY) ) } - output(PtCash.PROGRAM_ID) { outState.copy(amount = 1150.DOLLARS `issued by` defaultIssuer) } - command(ALICE_PUBKEY) { PtCash.Commands.Move() } + output(Cash.PROGRAM_ID) { outState.copy(amount = 1150.DOLLARS `issued by` defaultIssuer) } + command(ALICE_PUBKEY) { Cash.Commands.Move() } this `fails with` "the amounts balance" } // Can't have superfluous input states from different issuers. transaction { - attachment(PtCash.PROGRAM_ID) - input(PtCash.PROGRAM_ID) { inState } - input(PtCash.PROGRAM_ID) { inState `issued by` MINI_CORP } - output(PtCash.PROGRAM_ID) { outState } - command(ALICE_PUBKEY) { PtCash.Commands.Move() } + attachment(Cash.PROGRAM_ID) + input(Cash.PROGRAM_ID) { inState } + input(Cash.PROGRAM_ID) { inState `issued by` MINI_CORP } + output(Cash.PROGRAM_ID) { outState } + command(ALICE_PUBKEY) { Cash.Commands.Move() } this `fails with` "the amounts balance" } // Can't combine two different deposits at the same issuer. transaction { - attachment(PtCash.PROGRAM_ID) - input(PtCash.PROGRAM_ID) { inState } - input(PtCash.PROGRAM_ID) { inState.editDepositRef(3) } - output(PtCash.PROGRAM_ID) { outState.copy(amount = inState.amount * 2).editDepositRef(3) } - command(ALICE_PUBKEY) { PtCash.Commands.Move() } + 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) } + command(ALICE_PUBKEY) { Cash.Commands.Move() } this `fails with` "for reference [01]" } } @@ -417,22 +417,22 @@ class PtCashTests : TestDependencyInjectionBase() { fun exitLedger() { // Single input/output straightforward case. transaction { - attachment(PtCash.PROGRAM_ID) - input(PtCash.PROGRAM_ID) { issuerInState } - output(PtCash.PROGRAM_ID) { issuerInState.copy(amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)) } + attachment(Cash.PROGRAM_ID) + input(Cash.PROGRAM_ID) { issuerInState } + output(Cash.PROGRAM_ID) { issuerInState.copy(amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)) } tweak { - command(MEGA_CORP_PUBKEY) { PtCash.Commands.Exit(100.DOLLARS `issued by` defaultIssuer) } - command(MEGA_CORP_PUBKEY) { PtCash.Commands.Move() } + command(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(100.DOLLARS `issued by` defaultIssuer) } + command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() } this `fails with` "the amounts balance" } tweak { - command(MEGA_CORP_PUBKEY) { PtCash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) } - this `fails with` "required com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash.Commands.Move command" + command(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) } + this `fails with` "required com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash.Commands.Move command" tweak { - command(MEGA_CORP_PUBKEY) { PtCash.Commands.Move() } + command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() } this.verifies() } } @@ -443,21 +443,21 @@ class PtCashTests : TestDependencyInjectionBase() { fun `exit ledger with multiple issuers`() { // Multi-issuer case. transaction { - attachment(PtCash.PROGRAM_ID) - input(PtCash.PROGRAM_ID) { issuerInState } - input(PtCash.PROGRAM_ID) { issuerInState.copy(owner = MINI_CORP) `issued by` MINI_CORP } + attachment(Cash.PROGRAM_ID) + input(Cash.PROGRAM_ID) { issuerInState } + input(Cash.PROGRAM_ID) { issuerInState.copy(owner = MINI_CORP) `issued by` MINI_CORP } - output(PtCash.PROGRAM_ID) { issuerInState.copy(amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)) `issued by` MINI_CORP } - output(PtCash.PROGRAM_ID) { issuerInState.copy(owner = MINI_CORP, amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)) } + output(Cash.PROGRAM_ID) { issuerInState.copy(amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)) `issued by` MINI_CORP } + output(Cash.PROGRAM_ID) { issuerInState.copy(owner = MINI_CORP, amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)) } - command(MEGA_CORP_PUBKEY, MINI_CORP_PUBKEY) { PtCash.Commands.Move() } + command(MEGA_CORP_PUBKEY, MINI_CORP_PUBKEY) { Cash.Commands.Move() } this `fails with` "the amounts balance" - command(MEGA_CORP_PUBKEY) { PtCash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) } + command(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) } this `fails with` "the amounts balance" - command(MINI_CORP_PUBKEY) { PtCash.Commands.Exit(200.DOLLARS `issued by` MINI_CORP.ref(defaultRef)) } + command(MINI_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` MINI_CORP.ref(defaultRef)) } this.verifies() } } @@ -466,11 +466,11 @@ class PtCashTests : TestDependencyInjectionBase() { fun `exit cash not held by its issuer`() { // Single input/output straightforward case. transaction { - attachment(PtCash.PROGRAM_ID) - input(PtCash.PROGRAM_ID) { inState } - output(PtCash.PROGRAM_ID) { outState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) } - command(MEGA_CORP_PUBKEY) { PtCash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) } - command(ALICE_PUBKEY) { PtCash.Commands.Move() } + 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) } + command(ALICE_PUBKEY) { Cash.Commands.Move() } this `fails with` "the amounts balance" } } @@ -478,27 +478,27 @@ class PtCashTests : TestDependencyInjectionBase() { @Test fun multiIssuer() { transaction { - attachment(PtCash.PROGRAM_ID) + attachment(Cash.PROGRAM_ID) // Gather 2000 dollars from two different issuers. - input(PtCash.PROGRAM_ID) { inState } - input(PtCash.PROGRAM_ID) { inState `issued by` MINI_CORP } - command(ALICE_PUBKEY) { PtCash.Commands.Move() } + input(Cash.PROGRAM_ID) { inState } + input(Cash.PROGRAM_ID) { inState `issued by` MINI_CORP } + command(ALICE_PUBKEY) { Cash.Commands.Move() } // Can't merge them together. tweak { - output(PtCash.PROGRAM_ID) { inState.copy(owner = AnonymousParty(BOB_PUBKEY), amount = 2000.DOLLARS `issued by` defaultIssuer) } + output(Cash.PROGRAM_ID) { inState.copy(owner = AnonymousParty(BOB_PUBKEY), amount = 2000.DOLLARS `issued by` defaultIssuer) } this `fails with` "the amounts balance" } // Missing MiniCorp deposit tweak { - output(PtCash.PROGRAM_ID) { inState.copy(owner = AnonymousParty(BOB_PUBKEY)) } - output(PtCash.PROGRAM_ID) { inState.copy(owner = AnonymousParty(BOB_PUBKEY)) } + output(Cash.PROGRAM_ID) { inState.copy(owner = AnonymousParty(BOB_PUBKEY)) } + output(Cash.PROGRAM_ID) { inState.copy(owner = AnonymousParty(BOB_PUBKEY)) } this `fails with` "the amounts balance" } // This works. - output(PtCash.PROGRAM_ID) { inState.copy(owner = AnonymousParty(BOB_PUBKEY)) } - output(PtCash.PROGRAM_ID) { inState.copy(owner = AnonymousParty(BOB_PUBKEY)) `issued by` MINI_CORP } + output(Cash.PROGRAM_ID) { inState.copy(owner = AnonymousParty(BOB_PUBKEY)) } + output(Cash.PROGRAM_ID) { inState.copy(owner = AnonymousParty(BOB_PUBKEY)) `issued by` MINI_CORP } this.verifies() } } @@ -507,13 +507,13 @@ class PtCashTests : TestDependencyInjectionBase() { fun multiCurrency() { // Check we can do an atomic currency trade tx. transaction { - attachment(PtCash.PROGRAM_ID) - val pounds = PtCash.State(658.POUNDS `issued by` MINI_CORP.ref(3, 4, 5), AnonymousParty(BOB_PUBKEY)) - input(PtCash.PROGRAM_ID) { inState `owned by` AnonymousParty(ALICE_PUBKEY) } - input(PtCash.PROGRAM_ID) { pounds } - output(PtCash.PROGRAM_ID) { inState `owned by` AnonymousParty(BOB_PUBKEY) } - output(PtCash.PROGRAM_ID) { pounds `owned by` AnonymousParty(ALICE_PUBKEY) } - command(ALICE_PUBKEY, BOB_PUBKEY) { PtCash.Commands.Move() } + 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 } + output(Cash.PROGRAM_ID) { inState `owned by` AnonymousParty(BOB_PUBKEY) } + output(Cash.PROGRAM_ID) { pounds `owned by` AnonymousParty(ALICE_PUBKEY) } + command(ALICE_PUBKEY, BOB_PUBKEY) { Cash.Commands.Move() } this.verifies() } @@ -531,7 +531,7 @@ class PtCashTests : TestDependencyInjectionBase() { fun makeCash(amount: Amount, corp: Party, depositRef: Byte = 1) = StateAndRef( - TransactionState(PtCash.State(amount `issued by` corp.ref(depositRef), OUR_IDENTITY_1), PtCash.PROGRAM_ID, DUMMY_NOTARY), + TransactionState(Cash.State(amount `issued by` corp.ref(depositRef), OUR_IDENTITY_1), Cash.PROGRAM_ID, DUMMY_NOTARY), StateRef(SecureHash.randomSHA256(), Random().nextInt(32)) ) @@ -547,14 +547,14 @@ class PtCashTests : TestDependencyInjectionBase() { */ private fun makeExit(amount: Amount, corp: Party, depositRef: Byte = 1): WireTransaction { val tx = TransactionBuilder(DUMMY_NOTARY) - PtCash().generateExit(tx, Amount(amount.quantity, Issued(corp.ref(depositRef), amount.token)), WALLET) + Cash().generateExit(tx, Amount(amount.quantity, Issued(corp.ref(depositRef), amount.token)), WALLET) return tx.toWireTransaction(miniCorpServices) } private fun makeSpend(amount: Amount, dest: AbstractParty): WireTransaction { val tx = TransactionBuilder(DUMMY_NOTARY) database.transaction { - PtCash.generateSpend(miniCorpServices, tx, amount, dest) + Cash.generateSpend(miniCorpServices, tx, amount, dest) } return tx.toWireTransaction(miniCorpServices) } @@ -569,8 +569,8 @@ class PtCashTests : TestDependencyInjectionBase() { assertEquals(WALLET[0].ref, wtx.inputs[0]) assertEquals(0, wtx.outputs.size) - val expectedMove = PtCash.Commands.Move() - val expectedExit = PtCash.Commands.Exit(Amount(10000, Issued(MEGA_CORP.ref(1), USD))) + val expectedMove = Cash.Commands.Move() + val expectedExit = Cash.Commands.Exit(Amount(10000, Issued(MEGA_CORP.ref(1), USD))) assertEquals(listOf(expectedMove, expectedExit), wtx.commands.map { it.value }) } @@ -631,7 +631,7 @@ class PtCashTests : TestDependencyInjectionBase() { initialiseTestSerialization() assertFailsWith { val tx = TransactionBuilder(DUMMY_NOTARY) - PtCash().generateExit(tx, Amount(100, Issued(CHARLIE.ref(1), GBP)), emptyList()) + Cash().generateExit(tx, Amount(100, Issued(CHARLIE.ref(1), GBP)), emptyList()) } } @@ -647,7 +647,7 @@ class PtCashTests : TestDependencyInjectionBase() { val vaultState = vaultStatesUnconsumed.elementAt(0) assertEquals(vaultState.ref, wtx.inputs[0]) assertEquals(vaultState.state.data.copy(owner = THEIR_IDENTITY_1), wtx.getOutput(0)) - assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is PtCash.Commands.Move }.signers[0]) + assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) } } @@ -657,7 +657,7 @@ class PtCashTests : TestDependencyInjectionBase() { database.transaction { val tx = TransactionBuilder(DUMMY_NOTARY) - PtCash.generateSpend(miniCorpServices, tx, 80.DOLLARS, ALICE, setOf(MINI_CORP)) + Cash.generateSpend(miniCorpServices, tx, 80.DOLLARS, ALICE, setOf(MINI_CORP)) assertEquals(vaultStatesUnconsumed.elementAt(2).ref, tx.inputStates()[0]) } @@ -675,18 +675,18 @@ class PtCashTests : TestDependencyInjectionBase() { val vaultState = vaultStatesUnconsumed.elementAt(0) val changeAmount = 90.DOLLARS `issued by` defaultIssuer val likelyChangeState = wtx.outputs.map(TransactionState<*>::data).filter { state -> - if (state is PtCash.State) { + if (state is Cash.State) { state.amount == changeAmount } else { false } }.single() - val changeOwner = (likelyChangeState as PtCash.State).owner + val changeOwner = (likelyChangeState as Cash.State).owner assertEquals(1, miniCorpServices.keyManagementService.filterMyKeys(setOf(changeOwner.owningKey)).toList().size) assertEquals(vaultState.ref, wtx.inputs[0]) assertEquals(vaultState.state.data.copy(owner = THEIR_IDENTITY_1, amount = 10.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data) assertEquals(vaultState.state.data.copy(amount = changeAmount, owner = changeOwner), wtx.outputs[1].data) - assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is PtCash.Commands.Move }.signers[0]) + assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) } } @@ -704,7 +704,7 @@ class PtCashTests : TestDependencyInjectionBase() { assertEquals(vaultState0.ref, wtx.inputs[0]) assertEquals(vaultState1.ref, wtx.inputs[1]) assertEquals(vaultState0.state.data.copy(owner = THEIR_IDENTITY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.getOutput(0)) - assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is PtCash.Commands.Move }.signers[0]) + assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) } } @@ -718,15 +718,15 @@ class PtCashTests : TestDependencyInjectionBase() { wtx } database.transaction { - val vaultState0: StateAndRef = vaultStatesUnconsumed.elementAt(0) - val vaultState1: StateAndRef = vaultStatesUnconsumed.elementAt(1) - val vaultState2: StateAndRef = vaultStatesUnconsumed.elementAt(2) + val vaultState0: StateAndRef = vaultStatesUnconsumed.elementAt(0) + val vaultState1: StateAndRef = vaultStatesUnconsumed.elementAt(1) + val vaultState2: StateAndRef = vaultStatesUnconsumed.elementAt(2) assertEquals(vaultState0.ref, wtx.inputs[0]) assertEquals(vaultState1.ref, wtx.inputs[1]) assertEquals(vaultState2.ref, wtx.inputs[2]) assertEquals(vaultState0.state.data.copy(owner = THEIR_IDENTITY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[1].data) assertEquals(vaultState2.state.data.copy(owner = THEIR_IDENTITY_1), wtx.outputs[0].data) - assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is PtCash.Commands.Move }.signers[0]) + assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) } } @@ -751,9 +751,9 @@ class PtCashTests : TestDependencyInjectionBase() { */ @Test fun aggregation() { - val fiveThousandDollarsFromMega = PtCash.State(5000.DOLLARS `issued by` MEGA_CORP.ref(2), MEGA_CORP) - val twoThousandDollarsFromMega = PtCash.State(2000.DOLLARS `issued by` MEGA_CORP.ref(2), MINI_CORP) - val oneThousandDollarsFromMini = PtCash.State(1000.DOLLARS `issued by` MINI_CORP.ref(3), MEGA_CORP) + val fiveThousandDollarsFromMega = Cash.State(5000.DOLLARS `issued by` MEGA_CORP.ref(2), MEGA_CORP) + val twoThousandDollarsFromMega = Cash.State(2000.DOLLARS `issued by` MEGA_CORP.ref(2), MINI_CORP) + val oneThousandDollarsFromMini = Cash.State(1000.DOLLARS `issued by` MINI_CORP.ref(3), MEGA_CORP) // Obviously it must be possible to aggregate states with themselves assertEquals(fiveThousandDollarsFromMega.amount.token, fiveThousandDollarsFromMega.amount.token) @@ -767,7 +767,7 @@ class PtCashTests : TestDependencyInjectionBase() { // States cannot be aggregated if the currency differs assertNotEquals(oneThousandDollarsFromMini.amount.token, - PtCash.State(1000.POUNDS `issued by` MINI_CORP.ref(3), MEGA_CORP).amount.token) + Cash.State(1000.POUNDS `issued by` MINI_CORP.ref(3), MEGA_CORP).amount.token) // States cannot be aggregated if the reference differs assertNotEquals(fiveThousandDollarsFromMega.amount.token, (fiveThousandDollarsFromMega `with deposit` defaultIssuer).amount.token) @@ -777,9 +777,9 @@ class PtCashTests : TestDependencyInjectionBase() { @Test fun `summing by owner`() { val states = listOf( - PtCash.State(1000.DOLLARS `issued by` defaultIssuer, MINI_CORP), - PtCash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP), - PtCash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP) + Cash.State(1000.DOLLARS `issued by` defaultIssuer, MINI_CORP), + Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP), + Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP) ) assertEquals(6000.DOLLARS `issued by` defaultIssuer, states.sumCashBy(MEGA_CORP)) } @@ -787,31 +787,31 @@ class PtCashTests : TestDependencyInjectionBase() { @Test(expected = UnsupportedOperationException::class) fun `summing by owner throws`() { val states = listOf( - PtCash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP), - PtCash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP) + Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP), + Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP) ) states.sumCashBy(MINI_CORP) } @Test fun `summing no currencies`() { - val states = emptyList() + val states = emptyList() assertEquals(0.POUNDS `issued by` defaultIssuer, states.sumCashOrZero(GBP `issued by` defaultIssuer)) assertNull(states.sumCashOrNull()) } @Test(expected = UnsupportedOperationException::class) fun `summing no currencies throws`() { - val states = emptyList() + val states = emptyList() states.sumCash() } @Test fun `summing a single currency`() { val states = listOf( - PtCash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP), - PtCash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP), - PtCash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP) + Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP), + Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP), + Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP) ) // Test that summing everything produces the total number of dollars val expected = 7000.DOLLARS `issued by` defaultIssuer @@ -822,8 +822,8 @@ class PtCashTests : TestDependencyInjectionBase() { @Test(expected = IllegalArgumentException::class) fun `summing multiple currencies`() { val states = listOf( - PtCash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP), - PtCash.State(4000.POUNDS `issued by` defaultIssuer, MEGA_CORP) + Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP), + Cash.State(4000.POUNDS `issued by` defaultIssuer, MEGA_CORP) ) // Test that summing everything fails because we're mixing units states.sumCash() @@ -836,9 +836,9 @@ class PtCashTests : TestDependencyInjectionBase() { ledger(mockService) { unverifiedTransaction { - attachment(PtCash.PROGRAM_ID) - output(PtCash.PROGRAM_ID, "MEGA_CORP cash") { - PtCash.State( + attachment(Cash.PROGRAM_ID) + output(Cash.PROGRAM_ID, "MEGA_CORP cash") { + Cash.State( amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1), owner = MEGA_CORP ) @@ -846,20 +846,20 @@ class PtCashTests : TestDependencyInjectionBase() { } transaction { - attachment(PtCash.PROGRAM_ID) + attachment(Cash.PROGRAM_ID) input("MEGA_CORP cash") - output(PtCash.PROGRAM_ID, "MEGA_CORP cash 2", "MEGA_CORP cash".output().copy(owner = AnonymousParty(ALICE_PUBKEY)) ) - command(MEGA_CORP_PUBKEY) { PtCash.Commands.Move() } + 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(PtCash.PROGRAM_ID) + 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(PtCash.PROGRAM_ID, "MEGA_CORP cash 3", "MEGA_CORP cash".output().copy(owner = ALICE)) - command(MEGA_CORP_PUBKEY) { PtCash.Commands.Move() } + output(Cash.PROGRAM_ID, "MEGA_CORP cash 3", "MEGA_CORP cash".output().copy(owner = ALICE)) + command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() } this.verifies() } this.fails() @@ -878,10 +878,10 @@ class PtCashTests : TestDependencyInjectionBase() { PartyAndAmount(THEIR_IDENTITY_1, 400.DOLLARS), PartyAndAmount(THEIR_IDENTITY_2, 150.DOLLARS) ) - PtCash.generateSpend(miniCorpServices, tx, payments) + Cash.generateSpend(miniCorpServices, tx, payments) } val wtx = tx.toWireTransaction(miniCorpServices) - fun out(i: Int) = wtx.getOutput(i) as PtCash.State + fun out(i: Int) = wtx.getOutput(i) as Cash.State assertEquals(4, wtx.outputs.size) assertEquals(80.DOLLARS, out(0).amount.withoutIssuer()) assertEquals(320.DOLLARS, out(1).amount.withoutIssuer()) diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashExitFlowTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashExitFlowTests.kt index e65c743615..20e2b737cc 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashExitFlowTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashExitFlowTests.kt @@ -4,11 +4,11 @@ import net.corda.core.identity.Party import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow import com.r3.corda.enterprise.perftestcordapp.flows.PtCashException -import com.r3.corda.enterprise.perftestcordapp.flows.PtCashExitFlow -import com.r3.corda.enterprise.perftestcordapp.flows.PtCashIssueFlow +import com.r3.corda.enterprise.perftestcordapp.flows.CashExitFlow +import com.r3.corda.enterprise.perftestcordapp.flows.CashIssueFlow import com.r3.corda.enterprise.perftestcordapp.DOLLARS import com.r3.corda.enterprise.perftestcordapp.`issued by` -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash import net.corda.node.internal.StartedNode import net.corda.testing.chooseIdentity import net.corda.testing.getDefaultNotary @@ -43,7 +43,7 @@ class CashExitFlowTests { mockNet.runNetwork() notary = bankOfCordaNode.services.getDefaultNotary() - val future = bankOfCordaNode.services.startFlow(PtCashIssueFlow(initialBalance, ref, notary)).resultFuture + val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, notary)).resultFuture mockNet.runNetwork() future.getOrThrow() } @@ -57,20 +57,20 @@ class CashExitFlowTests { @Test fun `exit some cash`() { val exitAmount = 500.DOLLARS - val future = bankOfCordaNode.services.startFlow(PtCashExitFlow(exitAmount, ref)).resultFuture + val future = bankOfCordaNode.services.startFlow(CashExitFlow(exitAmount, ref)).resultFuture mockNet.runNetwork() val exitTx = future.getOrThrow().stx.tx val expected = (initialBalance - exitAmount).`issued by`(bankOfCorda.ref(ref)) assertEquals(1, exitTx.inputs.size) assertEquals(1, exitTx.outputs.size) - val output = exitTx.outputsOfType().single() + val output = exitTx.outputsOfType().single() assertEquals(expected, output.amount) } @Test fun `exit zero cash`() { val expected = 0.DOLLARS - val future = bankOfCordaNode.services.startFlow(PtCashExitFlow(expected, ref)).resultFuture + val future = bankOfCordaNode.services.startFlow(CashExitFlow(expected, ref)).resultFuture mockNet.runNetwork() assertFailsWith { future.getOrThrow() diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashIssueFlowTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashIssueFlowTests.kt index d355d264ff..3538535ba5 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashIssueFlowTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashIssueFlowTests.kt @@ -5,9 +5,9 @@ import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow import com.r3.corda.enterprise.perftestcordapp.DOLLARS import com.r3.corda.enterprise.perftestcordapp.`issued by` -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash import net.corda.node.internal.StartedNode -import com.r3.corda.enterprise.perftestcordapp.flows.PtCashIssueFlow +import com.r3.corda.enterprise.perftestcordapp.flows.CashIssueFlow import net.corda.testing.chooseIdentity import net.corda.testing.getDefaultNotary import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin @@ -49,10 +49,10 @@ class CashIssueFlowTests { fun `issue some cash`() { val expected = 500.DOLLARS val ref = OpaqueBytes.of(0x01) - val future = bankOfCordaNode.services.startFlow(PtCashIssueFlow(expected, ref, notary)).resultFuture + val future = bankOfCordaNode.services.startFlow(CashIssueFlow(expected, ref, notary)).resultFuture mockNet.runNetwork() val issueTx = future.getOrThrow().stx - val output = issueTx.tx.outputsOfType().single() + val output = issueTx.tx.outputsOfType().single() assertEquals(expected.`issued by`(bankOfCorda.ref(ref)), output.amount) } @@ -60,7 +60,7 @@ class CashIssueFlowTests { fun `issue zero cash`() { val expected = 0.DOLLARS val ref = OpaqueBytes.of(0x01) - val future = bankOfCordaNode.services.startFlow(PtCashIssueFlow(expected, ref, notary)).resultFuture + val future = bankOfCordaNode.services.startFlow(CashIssueFlow(expected, ref, notary)).resultFuture mockNet.runNetwork() assertFailsWith { future.getOrThrow() diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashPaymentFlowTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashPaymentFlowTests.kt index 5c1372d02c..ba3834947c 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashPaymentFlowTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashPaymentFlowTests.kt @@ -8,10 +8,10 @@ import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow import com.r3.corda.enterprise.perftestcordapp.DOLLARS import com.r3.corda.enterprise.perftestcordapp.`issued by` -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash import com.r3.corda.enterprise.perftestcordapp.flows.PtCashException -import com.r3.corda.enterprise.perftestcordapp.flows.PtCashIssueFlow -import com.r3.corda.enterprise.perftestcordapp.flows.PtCashPaymentFlow +import com.r3.corda.enterprise.perftestcordapp.flows.CashIssueFlow +import com.r3.corda.enterprise.perftestcordapp.flows.CashPaymentFlow import net.corda.node.internal.StartedNode import net.corda.testing.chooseIdentity import net.corda.testing.expect @@ -45,7 +45,7 @@ class CashPaymentFlowTests { bankOfCordaNode = nodes.partyNodes[0] bankOfCorda = bankOfCordaNode.info.chooseIdentity() notary = notaryNode.services.getDefaultNotary() - val future = bankOfCordaNode.services.startFlow(PtCashIssueFlow(initialBalance, ref, notary)).resultFuture + val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, notary)).resultFuture mockNet.runNetwork() future.getOrThrow() } @@ -64,10 +64,10 @@ class CashPaymentFlowTests { bankOfCordaNode.database.transaction { // Register for vault updates val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL) - val (_, vaultUpdatesBoc) = bankOfCordaNode.services.vaultQueryService.trackBy(criteria) - val (_, vaultUpdatesBankClient) = notaryNode.services.vaultQueryService.trackBy(criteria) + val (_, vaultUpdatesBoc) = bankOfCordaNode.services.vaultQueryService.trackBy(criteria) + val (_, vaultUpdatesBankClient) = notaryNode.services.vaultQueryService.trackBy(criteria) - val future = bankOfCordaNode.services.startFlow(PtCashPaymentFlow(expectedPayment, + val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expectedPayment, payTo)).resultFuture mockNet.runNetwork() future.getOrThrow() @@ -99,7 +99,7 @@ class CashPaymentFlowTests { fun `pay more than we have`() { val payTo = notaryNode.info.chooseIdentity() val expected = 4000.DOLLARS - val future = bankOfCordaNode.services.startFlow(PtCashPaymentFlow(expected, + val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expected, payTo)).resultFuture mockNet.runNetwork() assertFailsWith { @@ -111,7 +111,7 @@ class CashPaymentFlowTests { fun `pay zero cash`() { val payTo = notaryNode.info.chooseIdentity() val expected = 0.DOLLARS - val future = bankOfCordaNode.services.startFlow(PtCashPaymentFlow(expected, + val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expected, payTo)).resultFuture mockNet.runNetwork() assertFailsWith { diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/TwoPartyTradeFlowTest.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/TwoPartyTradeFlowTest.kt index c560f8d576..baf21c7eca 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/TwoPartyTradeFlowTest.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/TwoPartyTradeFlowTest.kt @@ -28,9 +28,9 @@ import net.corda.core.utilities.toNonEmptySet import net.corda.core.utilities.unwrap import com.r3.corda.enterprise.perftestcordapp.DOLLARS import com.r3.corda.enterprise.perftestcordapp.`issued by` -import com.r3.corda.enterprise.perftestcordapp.contracts.PtCommercialPaper +import com.r3.corda.enterprise.perftestcordapp.contracts.CommercialPaper import com.r3.corda.enterprise.perftestcordapp.contracts.asset.CASH -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PtCash +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash import com.r3.corda.enterprise.perftestcordapp.contracts.asset.`issued by` import com.r3.corda.enterprise.perftestcordapp.contracts.asset.`owned by` import com.r3.corda.enterprise.perftestcordapp.flows.TwoPartyTradeFlow.Buyer @@ -583,7 +583,7 @@ class TwoPartyTradeFlowTests { require(serviceHub.networkMapCache.isNotary(it.notaryIdentity)) { "${it.notaryIdentity} is not a notary" } it } - return subFlow(Buyer(sellerSession, notary, price, PtCommercialPaper.State::class.java, anonymous)) + return subFlow(Buyer(sellerSession, notary, price, CommercialPaper.State::class.java, anonymous)) } } @@ -680,13 +680,13 @@ class TwoPartyTradeFlowTests { // wants to sell to Bob. val eb1 = transaction(transactionBuilder = TransactionBuilder(notary = notary)) { // Issued money to itself. - output(PtCash.PROGRAM_ID, "elbonian money 1", notary = notary) { 800.DOLLARS.CASH `issued by` issuer `owned by` interimOwner } - output(PtCash.PROGRAM_ID, "elbonian money 2", notary = notary) { 1000.DOLLARS.CASH `issued by` issuer `owned by` interimOwner } + output(Cash.PROGRAM_ID, "elbonian money 1", notary = notary) { 800.DOLLARS.CASH `issued by` issuer `owned by` interimOwner } + output(Cash.PROGRAM_ID, "elbonian money 2", notary = notary) { 1000.DOLLARS.CASH `issued by` issuer `owned by` interimOwner } if (!withError) { - command(issuer.party.owningKey) { PtCash.Commands.Issue() } + command(issuer.party.owningKey) { Cash.Commands.Issue() } } else { // Put a broken command on so at least a signature is created - command(issuer.party.owningKey) { PtCash.Commands.Move() } + command(issuer.party.owningKey) { Cash.Commands.Move() } } timeWindow(TEST_TX_TIME) if (withError) { @@ -699,16 +699,16 @@ class TwoPartyTradeFlowTests { // Bob gets some cash onto the ledger from BoE val bc1 = transaction(transactionBuilder = TransactionBuilder(notary = notary)) { input("elbonian money 1") - output(PtCash.PROGRAM_ID, "bob cash 1", notary = notary) { 800.DOLLARS.CASH `issued by` issuer `owned by` owner } - command(interimOwner.owningKey) { PtCash.Commands.Move() } + output(Cash.PROGRAM_ID, "bob cash 1", notary = notary) { 800.DOLLARS.CASH `issued by` issuer `owned by` owner } + command(interimOwner.owningKey) { Cash.Commands.Move() } this.verifies() } val bc2 = transaction(transactionBuilder = TransactionBuilder(notary = notary)) { input("elbonian money 2") - output(PtCash.PROGRAM_ID, "bob cash 2", notary = notary) { 300.DOLLARS.CASH `issued by` issuer `owned by` owner } - output(PtCash.PROGRAM_ID, notary = notary) { 700.DOLLARS.CASH `issued by` issuer `owned by` interimOwner } // Change output. - command(interimOwner.owningKey) { PtCash.Commands.Move() } + output(Cash.PROGRAM_ID, "bob cash 2", notary = notary) { 300.DOLLARS.CASH `issued by` issuer `owned by` owner } + output(Cash.PROGRAM_ID, notary = notary) { 700.DOLLARS.CASH `issued by` issuer `owned by` interimOwner } // Change output. + command(interimOwner.owningKey) { Cash.Commands.Move() } this.verifies() } @@ -724,10 +724,10 @@ class TwoPartyTradeFlowTests { attachmentID: SecureHash?, notary: Party): Pair, List> { val ap = transaction(transactionBuilder = TransactionBuilder(notary = notary)) { - output(PtCommercialPaper.CP_PROGRAM_ID, "alice's paper", notary = notary) { - PtCommercialPaper.State(issuer, owner, amount, TEST_TX_TIME + 7.days) + output(CommercialPaper.CP_PROGRAM_ID, "alice's paper", notary = notary) { + CommercialPaper.State(issuer, owner, amount, TEST_TX_TIME + 7.days) } - command(issuer.party.owningKey) { PtCommercialPaper.Commands.Issue() } + command(issuer.party.owningKey) { CommercialPaper.Commands.Issue() } if (!withError) timeWindow(time = TEST_TX_TIME) if (attachmentID != null) From afac8c57287ec0855dba5a7c85c1bb5633e6f8b0 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Tue, 17 Oct 2017 14:07:43 +0100 Subject: [PATCH 11/15] Rename db tables to have pt in them so states for real cash and perf test cash don't get accidentally mixed up in the db --- .../contracts/asset/cash/selection/CashSelectionH2Impl.kt | 2 +- .../r3/corda/enterprise/perftestcordapp/schemas/CashSchemaV1.kt | 2 +- .../perftestcordapp/schemas/CommercialPaperSchemaV1.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/CashSelectionH2Impl.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/CashSelectionH2Impl.kt index 2bee35ba8a..94be9dd445 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/CashSelectionH2Impl.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/CashSelectionH2Impl.kt @@ -84,7 +84,7 @@ class CashSelectionH2Impl : CashSelection { // the softLockReserve update will detect whether we try to lock states locked by others val selectJoin = """ SELECT vs.transaction_id, vs.output_index, vs.contract_state, ccs.pennies, SET(@t, ifnull(@t,0)+ccs.pennies) total_pennies, vs.lock_id - FROM vault_states AS vs, contract_cash_states AS ccs + FROM vault_states AS vs, contract_pt_cash_states AS ccs WHERE vs.transaction_id = ccs.transaction_id AND vs.output_index = ccs.output_index AND vs.state_status = 0 AND ccs.ccy_code = '${amount.token}' and @t < ${amount.quantity} diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/CashSchemaV1.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/CashSchemaV1.kt index 21807a2873..9ec6f97ddf 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/CashSchemaV1.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/CashSchemaV1.kt @@ -22,7 +22,7 @@ object CashSchema @CordaSerializable object CashSchemaV1 : MappedSchema(schemaFamily = CashSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCashState::class.java)) { @Entity - @Table(name = "contract_cash_states", + @Table(name = "contract_pt_cash_states", indexes = arrayOf(Index(name = "ccy_code_idx", columnList = "ccy_code"), Index(name = "pennies_idx", columnList = "pennies"))) class PersistentCashState( diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/CommercialPaperSchemaV1.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/CommercialPaperSchemaV1.kt index f263750c1d..71857824f9 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/CommercialPaperSchemaV1.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/schemas/CommercialPaperSchemaV1.kt @@ -20,7 +20,7 @@ object CommercialPaperSchema @CordaSerializable object CommercialPaperSchemaV1 : MappedSchema(schemaFamily = CommercialPaperSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCommercialPaperState::class.java)) { @Entity - @Table(name = "cp_states", + @Table(name = "cp_pt_states", indexes = arrayOf(Index(name = "ccy_code_index", columnList = "ccy_code"), Index(name = "maturity_index", columnList = "maturity_instant"), Index(name = "face_value_index", columnList = "face_value"))) From cf6460592d254bf9f56729bac471c77b63ea8b67 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Tue, 17 Oct 2017 14:27:12 +0100 Subject: [PATCH 12/15] Replace references to finance packages in unit tests with perftest packages --- .../perftestcordapp/contracts/CommercialPaperTests.kt | 2 +- .../enterprise/perftestcordapp/contracts/asset/CashTests.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt index 21577cd447..bc53a924c6 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt @@ -216,7 +216,7 @@ class CommercialPaperTestsGeneric { // @Test @Ignore fun `issue move and then redeem`() { - setCordappPackages("net.corda.finance.contracts") + setCordappPackages("com.r3.enterprise.perftestcordapp.contracts") initialiseTestSerialization() val aliceDatabaseAndServices = MockServices.makeTestDatabaseAndMockServices(keys = listOf(ALICE_KEY)) val databaseAlice = aliceDatabaseAndServices.first diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt index c58ff06be3..3ab50223f9 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt @@ -832,7 +832,7 @@ class CashTests : TestDependencyInjectionBase() { // Double spend. @Test fun chainCashDoubleSpendFailsWith() { - val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), MEGA_CORP_KEY) + val mockService = MockServices(listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset"), MEGA_CORP_KEY) ledger(mockService) { unverifiedTransaction { From c70a7c374ecc1e6fc48f1e8f1037e5c57739ba34 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Thu, 19 Oct 2017 09:53:52 +0100 Subject: [PATCH 13/15] Pick up new implementation of H2CashSelection --- perftestcordapp/build.gradle | 12 +- .../perftestcordapp/contracts/asset/Cash.kt | 84 +----- .../contracts/asset/OnLedgerAsset.kt | 87 ++++-- .../cash/selection/AbstractCashSelection.kt | 168 +++++++++++ .../cash/selection/CashSelectionH2Impl.kt | 157 +++------- .../perftestcordapp/flows/AbstractCashFlow.kt | 4 +- .../flows/CashConfigDataFlow.kt | 59 ++-- .../perftestcordapp/flows/CashExitFlow.kt | 12 +- .../perftestcordapp/flows/CashPaymentFlow.kt | 2 +- ...sset.cash.selection.AbstractCashSelection} | 0 .../contracts/CommercialPaperTests.kt | 14 +- .../contracts/asset/CashSelectionH2Test.kt | 40 +++ .../contracts/asset/CashTests.kt | 108 +++---- .../flows/CashExitFlowTests.kt | 26 +- .../flows/CashIssueFlowTests.kt | 16 +- .../flows/CashPaymentFlowTests.kt | 31 +- .../flows/TwoPartyTradeFlowTest.kt | 275 +++++++++--------- 17 files changed, 615 insertions(+), 480 deletions(-) create mode 100644 perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/AbstractCashSelection.kt rename perftestcordapp/src/main/resources/META-INF/services/{com.r3.corda.enterprise.perftestcordapp.contracts.asset.CashSelection => com.r3.corda.enterprise.perftestcordapp.contracts.asset.cash.selection.AbstractCashSelection} (100%) create mode 100644 perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashSelectionH2Test.kt rename perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/{contracts => }/flows/CashExitFlowTests.kt (78%) rename perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/{contracts => }/flows/CashIssueFlowTests.kt (81%) rename perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/{contracts => }/flows/CashPaymentFlowTests.kt (83%) rename perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/{contracts => }/flows/TwoPartyTradeFlowTest.kt (79%) diff --git a/perftestcordapp/build.gradle b/perftestcordapp/build.gradle index d05eafb3ac..ba8c4a72b0 100644 --- a/perftestcordapp/build.gradle +++ b/perftestcordapp/build.gradle @@ -5,20 +5,26 @@ apply plugin: 'kotlin-jpa' apply plugin: CanonicalizerPlugin apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'net.corda.plugins.quasar-utils' -apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.cordapp' //apply plugin: 'com.jfrog.artifactory' description 'Corda performance test modules' dependencies { - // Note the :perftestflows module is a CorDapp in its own right - // and CorDapps using :perftestflows features should use 'cordapp' not 'compile' linkage. + // Note the :finance module is a CorDapp in its own right + // and CorDapps using :finance features should use 'cordapp' not 'compile' linkage. cordaCompile project(':core') cordaCompile project(':confidential-identities') + // TODO Remove this once we have app configs + compile "com.typesafe:config:$typesafe_config_version" + testCompile project(':test-utils') testCompile project(path: ':core', configuration: 'testArtifacts') testCompile "junit:junit:$junit_version" + + // AssertJ: for fluent assertions for testing + testCompile "org.assertj:assertj-core:$assertj_version" } configurations { diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/Cash.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/Cash.kt index a17ce3884a..583207edcd 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/Cash.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/Cash.kt @@ -17,78 +17,21 @@ import net.corda.core.schemas.PersistentState import net.corda.core.schemas.QueryableState import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder -import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.toBase58String import com.r3.corda.enterprise.perftestcordapp.schemas.CashSchemaV1 import com.r3.corda.enterprise.perftestcordapp.utils.sumCash import com.r3.corda.enterprise.perftestcordapp.utils.sumCashOrNull import com.r3.corda.enterprise.perftestcordapp.utils.sumCashOrZero +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.cash.selection.AbstractCashSelection import java.math.BigInteger import java.security.PublicKey -import java.sql.DatabaseMetaData import java.util.* -import java.util.concurrent.atomic.AtomicReference ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Cash // -/** - * Pluggable interface to allow for different cash selection provider implementations - * Default implementation [CashSelectionH2Impl] uses H2 database and a custom function within H2 to perform aggregation. - * Custom implementations must implement this interface and declare their implementation in - * META-INF/services/net.corda.contracts.asset.CashSelection - */ -interface CashSelection { - companion object { - val instance = AtomicReference() - - fun getInstance(metadata: () -> java.sql.DatabaseMetaData): CashSelection { - return instance.get() ?: { - val _metadata = metadata() - val cashSelectionAlgos = ServiceLoader.load(CashSelection::class.java).toList() - val cashSelectionAlgo = cashSelectionAlgos.firstOrNull { it.isCompatible(_metadata) } - cashSelectionAlgo?.let { - instance.set(cashSelectionAlgo) - cashSelectionAlgo - } ?: throw ClassNotFoundException("\nUnable to load compatible cash selection algorithm implementation for JDBC driver ($_metadata)." + - "\nPlease specify an implementation in META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.CashSelection") - }.invoke() - } - } - - /** - * Upon dynamically loading configured Cash Selection algorithms declared in META-INF/services - * this method determines whether the loaded implementation is compatible and usable with the currently - * loaded JDBC driver. - * Note: the first loaded implementation to pass this check will be used at run-time. - */ - fun isCompatible(metadata: DatabaseMetaData): Boolean - - /** - * Query to gather Cash states that are available - * @param services The service hub to allow access to the database session - * @param amount The amount of currency desired (ignoring issues, but specifying the currency) - * @param onlyFromIssuerParties If empty the operation ignores the specifics of the issuer, - * otherwise the set of eligible states wil be filtered to only include those from these issuers. - * @param notary If null the notary source is ignored, if specified then only states marked - * with this notary are included. - * @param lockId The FlowLogic.runId.uuid of the flow, which is used to soft reserve the states. - * Also, previous outputs of the flow will be eligible as they are implicitly locked with this id until the flow completes. - * @param withIssuerRefs If not empty the specific set of issuer references to match against. - * @return The matching states that were found. If sufficient funds were found these will be locked, - * otherwise what is available is returned unlocked for informational purposes. - */ - @Suspendable - fun unconsumedCashStatesForSpending(services: ServiceHub, - amount: Amount, - onlyFromIssuerParties: Set = emptySet(), - notary: Party? = null, - lockId: UUID, - withIssuerRefs: Set = emptySet()): List> -} - /** * A cash transaction may split and merge money represented by a set of (issuer, depositRef) pairs, across multiple * input and output states. Imagine a Bitcoin transaction but in which all UTXOs had a colour @@ -103,8 +46,8 @@ interface CashSelection { * vaults can ignore the issuer/depositRefs and just examine the amount fields. */ class Cash : OnLedgerAsset() { - override fun extractCommands(commands: Collection>): List> - = commands.select() + override fun extractCommands(commands: Collection>): List> + = commands.select() // DOCSTART 1 /** A state representing a cash claim against some party. */ @@ -126,10 +69,10 @@ class Cash : OnLedgerAsset() { override fun toString() = "${Emoji.bagOfCash}Cash($amount at ${amount.token.issuer} owned by $owner)" override fun withNewOwner(newOwner: AbstractParty) = CommandAndState(Commands.Move(), copy(owner = newOwner)) - fun ownedBy(owner: AbstractParty) = copy(owner = owner) - fun issuedBy(party: AbstractParty) = copy(amount = Amount(amount.quantity, amount.token.copy(issuer = amount.token.issuer.copy(party = party)))) - fun issuedBy(deposit: PartyAndReference) = copy(amount = Amount(amount.quantity, amount.token.copy(issuer = deposit))) - fun withDeposit(deposit: PartyAndReference): State = copy(amount = amount.copy(token = amount.token.copy(issuer = deposit))) + infix fun ownedBy(owner: AbstractParty) = copy(owner = owner) + infix fun issuedBy(party: AbstractParty) = copy(amount = Amount(amount.quantity, amount.token.copy(issuer = amount.token.issuer.copy(party = party)))) + infix fun issuedBy(deposit: PartyAndReference) = copy(amount = Amount(amount.quantity, amount.token.copy(issuer = deposit))) + infix fun withDeposit(deposit: PartyAndReference): Cash.State = copy(amount = amount.copy(token = amount.token.copy(issuer = deposit))) /** Object Relational Mapping support. */ override fun generateMappedObject(schema: MappedSchema): PersistentState { @@ -196,7 +139,7 @@ class Cash : OnLedgerAsset() { override fun verify(tx: LedgerTransaction) { // Each group is a set of input/output states with distinct (reference, currency) attributes. These types // of cash are not fungible and must be kept separated for bookkeeping purposes. - val groups = tx.groupStates { it: State -> it.amount.token } + val groups = tx.groupStates { it: Cash.State -> it.amount.token } for ((inputs, outputs, key) in groups) { // Either inputs or outputs could be empty. @@ -376,12 +319,12 @@ class Cash : OnLedgerAsset() { payments: List>, ourIdentity: PartyAndCertificate, onlyFromParties: Set = emptySet()): Pair> { - fun deriveState(txState: TransactionState, amt: Amount>, owner: AbstractParty) + fun deriveState(txState: TransactionState, amt: Amount>, owner: AbstractParty) = txState.copy(data = txState.data.copy(amount = amt, owner = owner)) // Retrieve unspent and unlocked cash states that meet our spending criteria. val totalAmount = payments.map { it.amount }.sumOrThrow() - val cashSelection = CashSelection.getInstance({ services.jdbcSession().metaData }) + val cashSelection = AbstractCashSelection.getInstance({ services.jdbcSession().metaData }) val acceptableCoins = cashSelection.unconsumedCashStatesForSpending(services, totalAmount, onlyFromParties, tx.notary, tx.lockId) val revocationEnabled = false // Revocation is currently unsupported // Generate a new identity that change will be sent to for confidentiality purposes. This means that a @@ -396,13 +339,6 @@ class Cash : OnLedgerAsset() { } } -// Small DSL extensions. - -/** @suppress */ infix fun Cash.State.`owned by`(owner: AbstractParty) = ownedBy(owner) -/** @suppress */ infix fun Cash.State.`issued by`(party: AbstractParty) = issuedBy(party) -/** @suppress */ infix fun Cash.State.`issued by`(deposit: PartyAndReference) = issuedBy(deposit) -/** @suppress */ infix fun Cash.State.`with deposit`(deposit: PartyAndReference): Cash.State = withDeposit(deposit) - // Unit testing helpers. These could go in a separate file but it's hardly worth it for just a few functions. /** A randomly generated key. */ diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/OnLedgerAsset.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/OnLedgerAsset.kt index 36f92469ed..190e3c6f7f 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/OnLedgerAsset.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/OnLedgerAsset.kt @@ -56,13 +56,13 @@ abstract class OnLedgerAsset> : C */ @Throws(InsufficientBalanceException::class) @JvmStatic - fun , T: Any> generateSpend(tx: TransactionBuilder, - amount: Amount, - to: AbstractParty, - acceptableStates: List>, - payChangeTo: AbstractParty, - deriveState: (TransactionState, Amount>, AbstractParty) -> TransactionState, - generateMoveCommand: () -> CommandData): Pair> { + fun , T : Any> generateSpend(tx: TransactionBuilder, + amount: Amount, + to: AbstractParty, + acceptableStates: List>, + payChangeTo: AbstractParty, + deriveState: (TransactionState, Amount>, AbstractParty) -> TransactionState, + generateMoveCommand: () -> CommandData): Pair> { return generateSpend(tx, listOf(PartyAndAmount(to, amount)), acceptableStates, payChangeTo, deriveState, generateMoveCommand) } @@ -92,12 +92,12 @@ abstract class OnLedgerAsset> : C */ @Throws(InsufficientBalanceException::class) @JvmStatic - fun , T: Any> generateSpend(tx: TransactionBuilder, - payments: List>, - acceptableStates: List>, - payChangeTo: AbstractParty, - deriveState: (TransactionState, Amount>, AbstractParty) -> TransactionState, - generateMoveCommand: () -> CommandData): Pair> { + fun , T : Any> generateSpend(tx: TransactionBuilder, + payments: List>, + acceptableStates: List>, + payChangeTo: AbstractParty, + deriveState: (TransactionState, Amount>, AbstractParty) -> TransactionState, + generateMoveCommand: () -> CommandData): Pair> { // Discussion // // This code is analogous to the Wallet.send() set of methods in bitcoinj, and has the same general outline. @@ -231,12 +231,36 @@ abstract class OnLedgerAsset> : C */ @Throws(InsufficientBalanceException::class) @JvmStatic + @Deprecated("Replaced with generateExit() which takes in a party to pay change to") fun , T: Any> generateExit(tx: TransactionBuilder, amountIssued: Amount>, assetStates: List>, deriveState: (TransactionState, Amount>, AbstractParty) -> TransactionState, generateMoveCommand: () -> CommandData, generateExitCommand: (Amount>) -> CommandData): Set { - val owner = assetStates.map { it.state.data.owner }.toSet().singleOrNull() ?: throw InsufficientBalanceException(amountIssued) + val owner = assetStates.map { it.state.data.owner }.toSet().firstOrNull() ?: throw InsufficientBalanceException(amountIssued) + return generateExit(tx, amountIssued, assetStates, owner, deriveState, generateMoveCommand, generateExitCommand) + } + + /** + * Generate an transaction exiting fungible assets from the ledger. + * + * @param tx transaction builder to add states and commands to. + * @param amountIssued the amount to be exited, represented as a quantity of issued currency. + * @param assetStates the asset states to take funds from. No checks are done about ownership of these states, it is + * the responsibility of the caller to check that they do not attempt to exit funds held by others. + * @param payChangeTo party to pay any change to; this is normally a confidential identity of the calling + * party. + * @return the public keys which must sign the transaction for it to be valid. + */ + @Throws(InsufficientBalanceException::class) + @JvmStatic + fun , T: Any> generateExit(tx: TransactionBuilder, amountIssued: Amount>, + assetStates: List>, + payChangeTo: AbstractParty, + deriveState: (TransactionState, Amount>, AbstractParty) -> TransactionState, + generateMoveCommand: () -> CommandData, + generateExitCommand: (Amount>) -> CommandData): Set { + require(assetStates.isNotEmpty()) { "List of states to exit cannot be empty." } val currency = amountIssued.token.product val amount = Amount(amountIssued.quantity, currency) var acceptableCoins = assetStates.filter { ref -> ref.state.data.amount.token == amountIssued.token } @@ -256,7 +280,7 @@ abstract class OnLedgerAsset> : C val outputs = if (change != null) { // Add a change output and adjust the last output downwards. - listOf(deriveState(gathered.last().state, change, owner)) + listOf(deriveState(gathered.last().state, change, payChangeTo)) } else emptyList() for (state in gathered) tx.addInputState(state) @@ -273,9 +297,9 @@ abstract class OnLedgerAsset> : C * wrappers around this function, which build the state for you, and those should be used in preference. */ @JvmStatic - fun , T: Any> generateIssue(tx: TransactionBuilder, - transactionState: TransactionState, - issueCommand: CommandData): Set { + fun , T : Any> generateIssue(tx: TransactionBuilder, + transactionState: TransactionState, + issueCommand: CommandData): Set { check(tx.inputStates().isEmpty()) check(tx.outputStates().map { it.data }.filterIsInstance(transactionState.javaClass).isEmpty()) require(transactionState.data.amount.quantity > 0) @@ -296,9 +320,12 @@ abstract class OnLedgerAsset> : C * @param amountIssued the amount to be exited, represented as a quantity of issued currency. * @param assetStates the asset states to take funds from. No checks are done about ownership of these states, it is * the responsibility of the caller to check that they do not exit funds held by others. + * @param payChangeTo party to pay any change to; this is normally a confidential identity of the calling + * party. * @return the public keys which must sign the transaction for it to be valid. */ @Throws(InsufficientBalanceException::class) + @Deprecated("Replaced with generateExit() which takes in a party to pay change to") fun generateExit(tx: TransactionBuilder, amountIssued: Amount>, assetStates: List>): Set { return generateExit( @@ -311,6 +338,30 @@ abstract class OnLedgerAsset> : C ) } + /** + * Generate an transaction exiting assets from the ledger. + * + * @param tx transaction builder to add states and commands to. + * @param amountIssued the amount to be exited, represented as a quantity of issued currency. + * @param assetStates the asset states to take funds from. No checks are done about ownership of these states, it is + * the responsibility of the caller to check that they do not exit funds held by others. + * @return the public keys which must sign the transaction for it to be valid. + */ + @Throws(InsufficientBalanceException::class) + fun generateExit(tx: TransactionBuilder, amountIssued: Amount>, + assetStates: List>, + payChangeTo: AbstractParty): Set { + return generateExit( + tx, + amountIssued, + assetStates, + payChangeTo, + deriveState = { state, amount, owner -> deriveState(state, amount, owner) }, + generateMoveCommand = { -> generateMoveCommand() }, + generateExitCommand = { amount -> generateExitCommand(amount) } + ) + } + abstract fun generateExitCommand(amount: Amount>): CommandData abstract fun generateMoveCommand(): MoveCommand diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/AbstractCashSelection.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/AbstractCashSelection.kt new file mode 100644 index 0000000000..8241e3c607 --- /dev/null +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/AbstractCashSelection.kt @@ -0,0 +1,168 @@ +package com.r3.corda.enterprise.perftestcordapp.contracts.asset.cash.selection + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.contracts.Amount +import net.corda.core.contracts.StateAndRef +import net.corda.core.contracts.StateRef +import net.corda.core.contracts.TransactionState +import net.corda.core.crypto.SecureHash +import net.corda.core.flows.FlowLogic +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.Party +import net.corda.core.node.ServiceHub +import net.corda.core.node.services.StatesNotAvailableException +import net.corda.core.serialization.SerializationDefaults +import net.corda.core.serialization.deserialize +import net.corda.core.utilities.* +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash +import java.sql.* +import java.util.* +import java.util.concurrent.atomic.AtomicReference +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + +/** + * Pluggable interface to allow for different cash selection provider implementations + * Default implementation [CashSelectionH2Impl] uses H2 database and a custom function within H2 to perform aggregation. + * Custom implementations must implement this interface and declare their implementation in + * META-INF/services/net.corda.contracts.asset.CashSelection + */ +abstract class AbstractCashSelection { + companion object { + val instance = AtomicReference() + + fun getInstance(metadata: () -> java.sql.DatabaseMetaData): AbstractCashSelection { + return instance.get() ?: { + val _metadata = metadata() + val cashSelectionAlgos = ServiceLoader.load(AbstractCashSelection::class.java).toList() + val cashSelectionAlgo = cashSelectionAlgos.firstOrNull { it.isCompatible(_metadata) } + cashSelectionAlgo?.let { + instance.set(cashSelectionAlgo) + cashSelectionAlgo + } ?: throw ClassNotFoundException("\nUnable to load compatible cash selection algorithm implementation for JDBC driver ($_metadata)." + + "\nPlease specify an implementation in META-INF/services/${AbstractCashSelection::class.java}") + }.invoke() + } + + val log = loggerFor() + } + + // coin selection retry loop counter, sleep (msecs) and lock for selecting states + // TODO: make parameters configurable when we get CorDapp configuration. + private val MAX_RETRIES = 8 + private val RETRY_SLEEP = 100 + private val RETRY_CAP = 2000 + private val spendLock: ReentrantLock = ReentrantLock() + + /** + * Upon dynamically loading configured Cash Selection algorithms declared in META-INF/services + * this method determines whether the loaded implementation is compatible and usable with the currently + * loaded JDBC driver. + * Note: the first loaded implementation to pass this check will be used at run-time. + */ + abstract fun isCompatible(metadata: DatabaseMetaData): Boolean + + /** + * A vendor specific query(ies) to gather Cash states that are available. + * @param statement The service hub to allow access to the database session + * @param amount The amount of currency desired (ignoring issues, but specifying the currency) + * @param lockId The FlowLogic.runId.uuid of the flow, which is used to soft reserve the states. + * Also, previous outputs of the flow will be eligible as they are implicitly locked with this id until the flow completes. + * @param notary If null the notary source is ignored, if specified then only states marked + * with this notary are included. + * @param onlyFromIssuerParties Optional issuer parties to match against. + * @param withIssuerRefs Optional issuer references to match against. + * @return JDBC ResultSet with the matching states that were found. If sufficient funds were found these will be locked, + * otherwise what is available is returned unlocked for informational purposes. + */ + abstract fun executeQuery(connection: Connection, amount: Amount, lockId: UUID, notary: Party?, + onlyFromIssuerParties: Set, withIssuerRefs: Set) : ResultSet + + override abstract fun toString() : String + + /** + * Query to gather Cash states that are available and retry if they are temporarily unavailable. + * @param services The service hub to allow access to the database session + * @param amount The amount of currency desired (ignoring issues, but specifying the currency) + * @param onlyFromIssuerParties If empty the operation ignores the specifics of the issuer, + * otherwise the set of eligible states wil be filtered to only include those from these issuers. + * @param notary If null the notary source is ignored, if specified then only states marked + * with this notary are included. + * @param lockId The FlowLogic.runId.uuid of the flow, which is used to soft reserve the states. + * Also, previous outputs of the flow will be eligible as they are implicitly locked with this id until the flow completes. + * @param withIssuerRefs If not empty the specific set of issuer references to match against. + * @return The matching states that were found. If sufficient funds were found these will be locked, + * otherwise what is available is returned unlocked for informational purposes. + */ + @Suspendable + fun unconsumedCashStatesForSpending(services: ServiceHub, + amount: Amount, + onlyFromIssuerParties: Set = emptySet(), + notary: Party? = null, + lockId: UUID, + withIssuerRefs: Set = emptySet()): List> { + val stateAndRefs = mutableListOf>() + + for (retryCount in 1..MAX_RETRIES) { + if (!attemptSpend(services, amount, lockId, notary, onlyFromIssuerParties, withIssuerRefs, stateAndRefs)) { + log.warn("Coin selection failed on attempt $retryCount") + // TODO: revisit the back off strategy for contended spending. + if (retryCount != MAX_RETRIES) { + stateAndRefs.clear() + val durationMillis = (minOf(RETRY_SLEEP.shl(retryCount), RETRY_CAP / 2) * (1.0 + Math.random())).toInt() + FlowLogic.sleep(durationMillis.millis) + } else { + log.warn("Insufficient spendable states identified for $amount") + } + } else { + break + } + } + return stateAndRefs + } + + private fun attemptSpend(services: ServiceHub, amount: Amount, lockId: UUID, notary: Party?, onlyFromIssuerParties: Set, withIssuerRefs: Set, stateAndRefs: MutableList>): Boolean { + spendLock.withLock { + val connection = services.jdbcSession() + try { + // we select spendable states irrespective of lock but prioritised by unlocked ones (Eg. null) + // the softLockReserve update will detect whether we try to lock states locked by others + val rs = executeQuery(connection, amount, lockId, notary, onlyFromIssuerParties, withIssuerRefs) + stateAndRefs.clear() + + var totalPennies = 0L + while (rs.next()) { + val txHash = SecureHash.parse(rs.getString(1)) + val index = rs.getInt(2) + val stateRef = StateRef(txHash, index) + val state = rs.getBlob(3).deserialize>(context = SerializationDefaults.STORAGE_CONTEXT) + val pennies = rs.getLong(4) + totalPennies = rs.getLong(5) + val rowLockId = rs.getString(6) + stateAndRefs.add(StateAndRef(state, stateRef)) + log.trace { "ROW: $rowLockId ($lockId): $stateRef : $pennies ($totalPennies)" } + } + + if (stateAndRefs.isNotEmpty() && totalPennies >= amount.quantity) { + // we should have a minimum number of states to satisfy our selection `amount` criteria + log.trace("Coin selection for $amount retrieved ${stateAndRefs.count()} states totalling $totalPennies pennies: $stateAndRefs") + + // With the current single threaded state machine available states are guaranteed to lock. + // TODO However, we will have to revisit these methods in the future multi-threaded. + services.vaultService.softLockReserve(lockId, (stateAndRefs.map { it.ref }).toNonEmptySet()) + return true + } + log.trace("Coin selection requested $amount but retrieved $totalPennies pennies with state refs: ${stateAndRefs.map { it.ref }}") + // retry as more states may become available + } catch (e: SQLException) { + log.error("""Failed retrieving unconsumed states for: amount [$amount], onlyFromIssuerParties [$onlyFromIssuerParties], notary [$notary], lockId [$lockId] + $e. + """) + } catch (e: StatesNotAvailableException) { // Should never happen with single threaded state machine + log.warn(e.message) + // retry only if there are locked states that may become available again (or consumed with change) + } + } + return false + } +} \ No newline at end of file diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/CashSelectionH2Impl.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/CashSelectionH2Impl.kt index 94be9dd445..c79737f531 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/CashSelectionH2Impl.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/CashSelectionH2Impl.kt @@ -1,29 +1,16 @@ package com.r3.corda.enterprise.perftestcordapp.contracts.asset.cash.selection - -import co.paralleluniverse.fibers.Suspendable -import co.paralleluniverse.strands.Strand -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.CashSelection import net.corda.core.contracts.Amount -import net.corda.core.contracts.StateAndRef -import net.corda.core.contracts.StateRef -import net.corda.core.contracts.TransactionState -import net.corda.core.crypto.SecureHash import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party -import net.corda.core.node.ServiceHub -import net.corda.core.node.services.StatesNotAvailableException -import net.corda.core.serialization.SerializationDefaults -import net.corda.core.serialization.deserialize import net.corda.core.utilities.* +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.cash.selection.AbstractCashSelection +import java.sql.Connection import java.sql.DatabaseMetaData -import java.sql.SQLException +import java.sql.ResultSet import java.util.* -import java.util.concurrent.locks.ReentrantLock -import kotlin.concurrent.withLock -class CashSelectionH2Impl : CashSelection { +class CashSelectionH2Impl : AbstractCashSelection() { companion object { const val JDBC_DRIVER_NAME = "H2 JDBC Driver" @@ -34,118 +21,48 @@ class CashSelectionH2Impl : CashSelection { return metadata.driverName == JDBC_DRIVER_NAME } - // coin selection retry loop counter, sleep (msecs) and lock for selecting states - private val MAX_RETRIES = 5 - private val RETRY_SLEEP = 100 - private val spendLock: ReentrantLock = ReentrantLock() + override fun toString() = "${this::class.java} for $JDBC_DRIVER_NAME" - /** - * An optimised query to gather Cash states that are available and retry if they are temporarily unavailable. - * @param services The service hub to allow access to the database session - * @param amount The amount of currency desired (ignoring issues, but specifying the currency) - * @param onlyFromIssuerParties If empty the operation ignores the specifics of the issuer, - * otherwise the set of eligible states wil be filtered to only include those from these issuers. - * @param notary If null the notary source is ignored, if specified then only states marked - * with this notary are included. - * @param lockId The FlowLogic.runId.uuid of the flow, which is used to soft reserve the states. - * Also, previous outputs of the flow will be eligible as they are implicitly locked with this id until the flow completes. - * @param withIssuerRefs If not empty the specific set of issuer references to match against. - * @return The matching states that were found. If sufficient funds were found these will be locked, - * otherwise what is available is returned unlocked for informational purposes. - */ - @Suspendable - override fun unconsumedCashStatesForSpending(services: ServiceHub, - amount: Amount, - onlyFromIssuerParties: Set, - notary: Party?, - lockId: UUID, - withIssuerRefs: Set): List> { - val issuerKeysStr = onlyFromIssuerParties.fold("") { left, right -> left + "('${right.owningKey.toBase58String()}')," }.dropLast(1) - val issuerRefsStr = withIssuerRefs.fold("") { left, right -> left + "('${right.bytes.toHexString()}')," }.dropLast(1) + // We are using an H2 specific means of selecting a minimum set of rows that match a request amount of coins: + // 1) There is no standard SQL mechanism of calculating a cumulative total on a field and restricting row selection on the + // running total of such an accumulator + // 2) H2 uses session variables to perform this accumulator function: + // http://www.h2database.com/html/functions.html#set + // 3) H2 does not support JOIN's in FOR UPDATE (hence we are forced to execute 2 queries) + override fun executeQuery(connection: Connection, amount: Amount, lockId: UUID, notary: Party?, + onlyFromIssuerParties: Set, withIssuerRefs: Set) : ResultSet { + connection.createStatement().execute("CALL SET(@t, 0);") - val stateAndRefs = mutableListOf>() - - // We are using an H2 specific means of selecting a minimum set of rows that match a request amount of coins: - // 1) There is no standard SQL mechanism of calculating a cumulative total on a field and restricting row selection on the - // running total of such an accumulator - // 2) H2 uses session variables to perform this accumulator function: - // http://www.h2database.com/html/functions.html#set - // 3) H2 does not support JOIN's in FOR UPDATE (hence we are forced to execute 2 queries) - - for (retryCount in 1..MAX_RETRIES) { - - spendLock.withLock { - val statement = services.jdbcSession().createStatement() - try { - statement.execute("CALL SET(@t, 0);") - - // we select spendable states irrespective of lock but prioritised by unlocked ones (Eg. null) - // the softLockReserve update will detect whether we try to lock states locked by others - val selectJoin = """ + val selectJoin = """ SELECT vs.transaction_id, vs.output_index, vs.contract_state, ccs.pennies, SET(@t, ifnull(@t,0)+ccs.pennies) total_pennies, vs.lock_id FROM vault_states AS vs, contract_pt_cash_states AS ccs WHERE vs.transaction_id = ccs.transaction_id AND vs.output_index = ccs.output_index AND vs.state_status = 0 - AND ccs.ccy_code = '${amount.token}' and @t < ${amount.quantity} - AND (vs.lock_id = '$lockId' OR vs.lock_id is null) + AND ccs.ccy_code = ? and @t < ? + AND (vs.lock_id = ? OR vs.lock_id is null) """ + - (if (notary != null) - " AND vs.notary_name = '${notary.name}'" else "") + - (if (onlyFromIssuerParties.isNotEmpty()) - " AND ccs.issuer_key IN ($issuerKeysStr)" else "") + - (if (withIssuerRefs.isNotEmpty()) - " AND ccs.issuer_ref IN ($issuerRefsStr)" else "") + (if (notary != null) + " AND vs.notary_name = ?" else "") + + (if (onlyFromIssuerParties.isNotEmpty()) + " AND ccs.issuer_key IN (?)" else "") + + (if (withIssuerRefs.isNotEmpty()) + " AND ccs.issuer_ref IN (?)" else "") - // Retrieve spendable state refs - val rs = statement.executeQuery(selectJoin) - stateAndRefs.clear() - log.debug(selectJoin) - var totalPennies = 0L - while (rs.next()) { - val txHash = SecureHash.parse(rs.getString(1)) - val index = rs.getInt(2) - val stateRef = StateRef(txHash, index) - val state = rs.getBytes(3).deserialize>(context = SerializationDefaults.STORAGE_CONTEXT) - val pennies = rs.getLong(4) - totalPennies = rs.getLong(5) - val rowLockId = rs.getString(6) - stateAndRefs.add(StateAndRef(state, stateRef)) - log.trace { "ROW: $rowLockId ($lockId): $stateRef : $pennies ($totalPennies)" } - } + // Use prepared statement for protection against SQL Injection (http://www.h2database.com/html/advanced.html#sql_injection) + val psSelectJoin = connection.prepareStatement(selectJoin) + var pIndex = 0 + psSelectJoin.setString(++pIndex, amount.token.currencyCode) + psSelectJoin.setLong(++pIndex, amount.quantity) + psSelectJoin.setString(++pIndex, lockId.toString()) + if (notary != null) + psSelectJoin.setString(++pIndex, notary.name.toString()) + if (onlyFromIssuerParties.isNotEmpty()) + psSelectJoin.setObject(++pIndex, onlyFromIssuerParties.map { it.owningKey.toBase58String() as Any}.toTypedArray() ) + if (withIssuerRefs.isNotEmpty()) + psSelectJoin.setObject(++pIndex, withIssuerRefs.map { it.bytes.toHexString() as Any }.toTypedArray()) + log.debug { psSelectJoin.toString() } - if (stateAndRefs.isNotEmpty() && totalPennies >= amount.quantity) { - // we should have a minimum number of states to satisfy our selection `amount` criteria - log.trace("Coin selection for $amount retrieved ${stateAndRefs.count()} states totalling $totalPennies pennies: $stateAndRefs") - - // With the current single threaded state machine available states are guaranteed to lock. - // TODO However, we will have to revisit these methods in the future multi-threaded. - services.vaultService.softLockReserve(lockId, (stateAndRefs.map { it.ref }).toNonEmptySet()) - return stateAndRefs - } - log.trace("Coin selection requested $amount but retrieved $totalPennies pennies with state refs: ${stateAndRefs.map { it.ref }}") - // retry as more states may become available - } catch (e: SQLException) { - log.error("""Failed retrieving unconsumed states for: amount [$amount], onlyFromIssuerParties [$onlyFromIssuerParties], notary [$notary], lockId [$lockId] - $e. - """) - } catch (e: StatesNotAvailableException) { // Should never happen with single threaded state machine - stateAndRefs.clear() - log.warn(e.message) - // retry only if there are locked states that may become available again (or consumed with change) - } finally { - statement.close() - } - } - - log.warn("Coin selection failed on attempt $retryCount") - // TODO: revisit the back off strategy for contended spending. - if (retryCount != MAX_RETRIES) { - Strand.sleep(RETRY_SLEEP * retryCount.toLong()) - } - } - - log.warn("Insufficient spendable states identified for $amount") - return stateAndRefs + return psSelectJoin.executeQuery() } } \ No newline at end of file diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/AbstractCashFlow.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/AbstractCashFlow.kt index d1241e8ed3..119b99d0ea 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/AbstractCashFlow.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/AbstractCashFlow.kt @@ -31,7 +31,7 @@ abstract class AbstractCashFlow(override val progressTracker: ProgressTra try { return subFlow(FinalityFlow(tx, extraParticipants)) } catch (e: NotaryException) { - throw PtCashException(message, e) + throw CashException(message, e) } } @@ -50,4 +50,4 @@ abstract class AbstractCashFlow(override val progressTracker: ProgressTra abstract class AbstractRequest(val amount: Amount) } -class PtCashException(message: String, cause: Throwable) : FlowException(message, cause) \ No newline at end of file +class CashException(message: String, cause: Throwable) : FlowException(message, cause) \ No newline at end of file diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashConfigDataFlow.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashConfigDataFlow.kt index cb4ec6d6a2..4bb1af7659 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashConfigDataFlow.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashConfigDataFlow.kt @@ -1,40 +1,61 @@ package com.r3.corda.enterprise.perftestcordapp.flows import co.paralleluniverse.fibers.Suspendable -import net.corda.core.flows.FlowException +import com.typesafe.config.ConfigFactory import net.corda.core.flows.FlowLogic import net.corda.core.flows.StartableByRPC +import net.corda.core.internal.declaredField +import net.corda.core.internal.div +import net.corda.core.internal.read +import net.corda.core.node.AppServiceHub +import net.corda.core.node.services.CordaService import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.SingletonSerializeAsToken import com.r3.corda.enterprise.perftestcordapp.CHF import com.r3.corda.enterprise.perftestcordapp.EUR import com.r3.corda.enterprise.perftestcordapp.GBP import com.r3.corda.enterprise.perftestcordapp.USD +import com.r3.corda.enterprise.perftestcordapp.flows.ConfigHolder.Companion.supportedCurrencies +import java.nio.file.Path import java.util.* +// TODO Until apps have access to their own config, we'll hack things by first getting the baseDirectory, read the node.conf +// again to get our config and store it here for access by our flow +@CordaService +class ConfigHolder(services: AppServiceHub) : SingletonSerializeAsToken() { + companion object { + val supportedCurrencies = listOf(USD, GBP, CHF, EUR) + } + + val issuableCurrencies: List + + init { + // Warning!! You are about to see a major hack! + val baseDirectory = services.declaredField("serviceHub").value + .let { it.javaClass.getMethod("getConfiguration").apply { isAccessible = true }.invoke(it) } + .declaredField("baseDirectory").value + val config = (baseDirectory / "node.conf").read { ConfigFactory.parseReader(it.reader()) } + if (config.hasPath("issuableCurrencies")) { + issuableCurrencies = config.getStringList("issuableCurrencies").map { Currency.getInstance(it) } + require(supportedCurrencies.containsAll(issuableCurrencies)) + } else { + issuableCurrencies = emptyList() + } + } +} + + /** * Flow to obtain cash cordapp app configuration. */ @StartableByRPC -class CashConfigDataFlow : FlowLogic() { - companion object { - private val supportedCurrencies = listOf(USD, GBP, CHF, EUR) - } - +class CashConfigDataFlow : FlowLogic() { @Suspendable - override fun call(): PtCashConfiguration { - val issuableCurrencies = supportedCurrencies.mapNotNull { - try { - // Currently it uses checkFlowPermission to determine the list of issuable currency as a temporary hack. - // TODO: get the config from proper configuration source. - checkFlowPermission("corda.issuer.$it", emptyMap()) - it - } catch (e: FlowException) { - null - } - } - return PtCashConfiguration(issuableCurrencies, supportedCurrencies) + override fun call(): CashConfiguration { + val configHolder = serviceHub.cordaService(ConfigHolder::class.java) + return CashConfiguration(configHolder.issuableCurrencies, supportedCurrencies) } } @CordaSerializable -data class PtCashConfiguration(val issuableCurrencies: List, val supportedCurrencies: List) +data class CashConfiguration(val issuableCurrencies: List, val supportedCurrencies: List) diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashExitFlow.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashExitFlow.kt index 53b8727a7d..fad3ec7b58 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashExitFlow.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashExitFlow.kt @@ -14,7 +14,7 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.ProgressTracker import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.CashSelection +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.cash.selection.AbstractCashSelection import com.r3.corda.enterprise.perftestcordapp.issuedBy import java.util.* @@ -41,12 +41,12 @@ class CashExitFlow(private val amount: Amount, * (for this flow this map is always empty). */ @Suspendable - @Throws(PtCashException::class) + @Throws(CashException::class) override fun call(): AbstractCashFlow.Result { progressTracker.currentStep = GENERATING_TX val builder = TransactionBuilder(notary = null) val issuer = ourIdentity.ref(issuerRef) - val exitStates = CashSelection + val exitStates = AbstractCashSelection .getInstance { serviceHub.jdbcSession().metaData } .unconsumedCashStatesForSpending(serviceHub, amount, setOf(issuer.party), builder.notary, builder.lockId, setOf(issuer.reference)) val signers = try { @@ -55,12 +55,12 @@ class CashExitFlow(private val amount: Amount, amount.issuedBy(issuer), exitStates) } catch (e: InsufficientBalanceException) { - throw PtCashException("Exiting more cash than exists", e) + throw CashException("Exiting more cash than exists", e) } // Work out who the owners of the burnt states were (specify page size so we don't silently drop any if > DEFAULT_PAGE_SIZE) - val inputStates = serviceHub.vaultQueryService.queryBy(VaultQueryCriteria(stateRefs = builder.inputStates()), - PageSpecification(pageNumber = DEFAULT_PAGE_NUM, pageSize = builder.inputStates().size)).states + val inputStates = serviceHub.vaultService.queryBy(VaultQueryCriteria(stateRefs = builder.inputStates()), + PageSpecification(pageNumber = DEFAULT_PAGE_NUM, pageSize = builder.inputStates().size)).states // TODO: Is it safe to drop participants we don't know how to contact? Does not knowing how to contact them // count as a reason to fail? diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlow.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlow.kt index 92b19ea5b5..b60c83ced0 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlow.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlow.kt @@ -55,7 +55,7 @@ open class CashPaymentFlow( anonymousRecipient, issuerConstraint) } catch (e: InsufficientBalanceException) { - throw PtCashException("Insufficient cash for spend: ${e.message}", e) + throw CashException("Insufficient cash for spend: ${e.message}", e) } progressTracker.currentStep = SIGNING_TX diff --git a/perftestcordapp/src/main/resources/META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.CashSelection b/perftestcordapp/src/main/resources/META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.cash.selection.AbstractCashSelection similarity index 100% rename from perftestcordapp/src/main/resources/META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.CashSelection rename to perftestcordapp/src/main/resources/META-INF/services/com.r3.corda.enterprise.perftestcordapp.contracts.asset.cash.selection.AbstractCashSelection diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt index bc53a924c6..dade7aa141 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt @@ -15,6 +15,7 @@ import com.r3.corda.enterprise.perftestcordapp.contracts.asset.* import net.corda.testing.* import com.r3.corda.enterprise.perftestcordapp.contracts.asset.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 @@ -81,8 +82,8 @@ class CommercialPaperTestsGeneric { 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) + output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE) + output(Cash.PROGRAM_ID, "some profits", someProfits.STATE ownedBy MEGA_CORP) } // Some CP is issued onto the ledger by MegaCorp. @@ -100,7 +101,7 @@ class CommercialPaperTestsGeneric { attachments(Cash.PROGRAM_ID, CommercialPaper.CP_PROGRAM_ID) input("paper") input("alice's $900") - output(Cash.PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP } + output(Cash.PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH issuedBy issuer ownedBy MEGA_CORP } output(thisTest.getContract(), "alice's paper") { "paper".output().withOwner(ALICE) } command(ALICE_PUBKEY) { Cash.Commands.Move() } command(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() } @@ -115,8 +116,8 @@ class CommercialPaperTestsGeneric { input("some profits") fun TransactionDSL.outputs(aliceGetsBack: Amount>) { - output(Cash.PROGRAM_ID, "Alice's profit") { aliceGetsBack.STATE `owned by` ALICE } - output(Cash.PROGRAM_ID, "Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP } + output(Cash.PROGRAM_ID, "Alice's profit") { aliceGetsBack.STATE ownedBy ALICE } + output(Cash.PROGRAM_ID, "Change") { (someProfits - aliceGetsBack).STATE ownedBy MEGA_CORP } } command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() } @@ -216,7 +217,6 @@ class CommercialPaperTestsGeneric { // @Test @Ignore fun `issue move and then redeem`() { - setCordappPackages("com.r3.enterprise.perftestcordapp.contracts") initialiseTestSerialization() val aliceDatabaseAndServices = MockServices.makeTestDatabaseAndMockServices(keys = listOf(ALICE_KEY)) val databaseAlice = aliceDatabaseAndServices.first @@ -300,5 +300,3 @@ class CommercialPaperTestsGeneric { resetTestSerialization() } } - - diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashSelectionH2Test.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashSelectionH2Test.kt new file mode 100644 index 0000000000..8929387a9e --- /dev/null +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashSelectionH2Test.kt @@ -0,0 +1,40 @@ +package com.r3.corda.enterprise.perftestcordapp.contracts.asset + +import net.corda.core.utilities.getOrThrow +import com.r3.corda.enterprise.perftestcordapp.DOLLARS +import com.r3.corda.enterprise.perftestcordapp.flows.CashException +import com.r3.corda.enterprise.perftestcordapp.flows.CashPaymentFlow +import net.corda.testing.chooseIdentity +import net.corda.testing.node.MockNetwork +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.Test + + +class CashSelectionH2Test { + + @Test + fun `check does not hold connection over retries`() { + val mockNet = MockNetwork(threadPerNode = true) + try { + val notaryNode = mockNet.createNotaryNode() + val bankA = mockNet.createNode(configOverrides = { existingConfig -> + // Tweak connections to be minimal to make this easier (1 results in a hung node during start up, so use 2 connections). + existingConfig.dataSourceProperties.setProperty("maximumPoolSize", "2") + existingConfig + }) + + mockNet.startNodes() + + // Start more cash spends than we have connections. If spend leaks a connection on retry, we will run out of connections. + val flow1 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notaryNode.info.chooseIdentity())) + val flow2 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notaryNode.info.chooseIdentity())) + val flow3 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notaryNode.info.chooseIdentity())) + + assertThatThrownBy { flow1.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) + assertThatThrownBy { flow2.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) + assertThatThrownBy { flow3.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) + } finally { + mockNet.stopNodes() + } + } +} \ No newline at end of file diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt index 3ab50223f9..8a67039948 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt @@ -11,6 +11,7 @@ import net.corda.core.crypto.SecureHash import net.corda.core.crypto.generateKeyPair 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.node.ServiceHub import net.corda.core.node.services.Vault @@ -80,25 +81,25 @@ fun ServiceHub.fillWithSomeTestCash(howMuch: Amount, class CashTests : TestDependencyInjectionBase() { - val defaultRef = OpaqueBytes(ByteArray(1, { 1 })) - val defaultIssuer = MEGA_CORP.ref(defaultRef) - val inState = Cash.State( + private val defaultRef = OpaqueBytes(ByteArray(1, { 1 })) + private val defaultIssuer = MEGA_CORP.ref(defaultRef) + private val inState = Cash.State( amount = 1000.DOLLARS `issued by` defaultIssuer, owner = AnonymousParty(ALICE_PUBKEY) ) // Input state held by the issuer - val issuerInState = inState.copy(owner = defaultIssuer.party) - val outState = issuerInState.copy(owner = AnonymousParty(BOB_PUBKEY)) + private val issuerInState = inState.copy(owner = defaultIssuer.party) + private val outState = issuerInState.copy(owner = AnonymousParty(BOB_PUBKEY)) - fun Cash.State.editDepositRef(ref: Byte) = copy( + private fun Cash.State.editDepositRef(ref: Byte) = copy( amount = Amount(amount.quantity, token = amount.token.copy(amount.token.issuer.copy(reference = OpaqueBytes.of(ref)))) ) - lateinit var miniCorpServices: MockServices - lateinit var megaCorpServices: MockServices + private lateinit var miniCorpServices: MockServices + private lateinit var megaCorpServices: MockServices val vault: VaultService get() = miniCorpServices.vaultService lateinit var database: CordaPersistence - lateinit var vaultStatesUnconsumed: List> + private lateinit var vaultStatesUnconsumed: List> @Before fun setUp() { @@ -120,7 +121,7 @@ class CashTests : TestDependencyInjectionBase() { ownedBy = OUR_IDENTITY_1, issuedBy = MINI_CORP.ref(1), issuerServices = miniCorpServices) } database.transaction { - vaultStatesUnconsumed = miniCorpServices.vaultQueryService.queryBy().states + vaultStatesUnconsumed = miniCorpServices.vaultService.queryBy().states } resetTestSerialization() } @@ -154,7 +155,7 @@ class CashTests : TestDependencyInjectionBase() { } tweak { output(Cash.PROGRAM_ID) { outState } - output(Cash.PROGRAM_ID) { outState `issued by` MINI_CORP } + output(Cash.PROGRAM_ID) { outState issuedBy MINI_CORP } command(ALICE_PUBKEY) { Cash.Commands.Move() } this `fails with` "at least one cash input" } @@ -358,7 +359,7 @@ class CashTests : TestDependencyInjectionBase() { transaction { attachment(Cash.PROGRAM_ID) input(Cash.PROGRAM_ID) { inState } - output(Cash.PROGRAM_ID) { outState `issued by` MINI_CORP } + output(Cash.PROGRAM_ID) { outState issuedBy MINI_CORP } command(ALICE_PUBKEY) { Cash.Commands.Move() } this `fails with` "the amounts balance" } @@ -397,7 +398,7 @@ class CashTests : TestDependencyInjectionBase() { transaction { attachment(Cash.PROGRAM_ID) input(Cash.PROGRAM_ID) { inState } - input(Cash.PROGRAM_ID) { inState `issued by` MINI_CORP } + input(Cash.PROGRAM_ID) { inState issuedBy MINI_CORP } output(Cash.PROGRAM_ID) { outState } command(ALICE_PUBKEY) { Cash.Commands.Move() } this `fails with` "the amounts balance" @@ -445,9 +446,9 @@ class CashTests : TestDependencyInjectionBase() { transaction { attachment(Cash.PROGRAM_ID) input(Cash.PROGRAM_ID) { issuerInState } - input(Cash.PROGRAM_ID) { issuerInState.copy(owner = MINI_CORP) `issued by` MINI_CORP } + input(Cash.PROGRAM_ID) { issuerInState.copy(owner = MINI_CORP) issuedBy MINI_CORP } - output(Cash.PROGRAM_ID) { issuerInState.copy(amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)) `issued by` MINI_CORP } + output(Cash.PROGRAM_ID) { issuerInState.copy(amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)) issuedBy MINI_CORP } output(Cash.PROGRAM_ID) { issuerInState.copy(owner = MINI_CORP, amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)) } command(MEGA_CORP_PUBKEY, MINI_CORP_PUBKEY) { Cash.Commands.Move() } @@ -481,7 +482,7 @@ class CashTests : TestDependencyInjectionBase() { 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 } + input(Cash.PROGRAM_ID) { inState issuedBy MINI_CORP } command(ALICE_PUBKEY) { Cash.Commands.Move() } // Can't merge them together. @@ -498,7 +499,7 @@ class CashTests : TestDependencyInjectionBase() { // This works. output(Cash.PROGRAM_ID) { inState.copy(owner = AnonymousParty(BOB_PUBKEY)) } - output(Cash.PROGRAM_ID) { inState.copy(owner = AnonymousParty(BOB_PUBKEY)) `issued by` MINI_CORP } + output(Cash.PROGRAM_ID) { inState.copy(owner = AnonymousParty(BOB_PUBKEY)) issuedBy MINI_CORP } this.verifies() } } @@ -509,10 +510,10 @@ class CashTests : TestDependencyInjectionBase() { 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) { inState ownedBy AnonymousParty(ALICE_PUBKEY) } input(Cash.PROGRAM_ID) { pounds } - output(Cash.PROGRAM_ID) { inState `owned by` AnonymousParty(BOB_PUBKEY) } - output(Cash.PROGRAM_ID) { pounds `owned by` AnonymousParty(ALICE_PUBKEY) } + output(Cash.PROGRAM_ID) { inState ownedBy AnonymousParty(BOB_PUBKEY) } + output(Cash.PROGRAM_ID) { pounds ownedBy AnonymousParty(ALICE_PUBKEY) } command(ALICE_PUBKEY, BOB_PUBKEY) { Cash.Commands.Move() } this.verifies() @@ -523,19 +524,20 @@ class CashTests : TestDependencyInjectionBase() { // // Spend tx generation - val OUR_KEY: KeyPair by lazy { generateKeyPair() } - val OUR_IDENTITY_1: AbstractParty get() = AnonymousParty(OUR_KEY.public) + private val OUR_KEY: KeyPair by lazy { generateKeyPair() } + private val OUR_IDENTITY_1: AbstractParty get() = AnonymousParty(OUR_KEY.public) + private val OUR_IDENTITY_AND_CERT = getTestPartyAndCertificate(CordaX500Name(organisation = "Me", locality = "London", country = "GB"), OUR_KEY.public) - val THEIR_IDENTITY_1 = AnonymousParty(MINI_CORP_PUBKEY) - val THEIR_IDENTITY_2 = AnonymousParty(CHARLIE_PUBKEY) + private val THEIR_IDENTITY_1 = AnonymousParty(MINI_CORP_PUBKEY) + private val THEIR_IDENTITY_2 = AnonymousParty(CHARLIE_PUBKEY) - fun makeCash(amount: Amount, corp: Party, depositRef: Byte = 1) = + private fun makeCash(amount: Amount, issuer: AbstractParty, depositRef: Byte = 1) = StateAndRef( - TransactionState(Cash.State(amount `issued by` corp.ref(depositRef), OUR_IDENTITY_1), Cash.PROGRAM_ID, DUMMY_NOTARY), + TransactionState(Cash.State(amount `issued by` issuer.ref(depositRef), OUR_IDENTITY_1), Cash.PROGRAM_ID, DUMMY_NOTARY), StateRef(SecureHash.randomSHA256(), Random().nextInt(32)) ) - val WALLET = listOf( + private val WALLET = listOf( makeCash(100.DOLLARS, MEGA_CORP), makeCash(400.DOLLARS, MEGA_CORP), makeCash(80.DOLLARS, MINI_CORP), @@ -545,16 +547,17 @@ class CashTests : TestDependencyInjectionBase() { /** * Generate an exit transaction, removing some amount of cash from the ledger. */ - private fun makeExit(amount: Amount, corp: Party, depositRef: Byte = 1): WireTransaction { + private fun makeExit(serviceHub: ServiceHub, amount: Amount, issuer: 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(miniCorpServices) + val payChangeTo = serviceHub.keyManagementService.freshKeyAndCert(MINI_CORP_IDENTITY, false).party + Cash().generateExit(tx, Amount(amount.quantity, Issued(issuer.ref(depositRef), amount.token)), WALLET, payChangeTo) + return tx.toWireTransaction(serviceHub) } private fun makeSpend(amount: Amount, dest: AbstractParty): WireTransaction { val tx = TransactionBuilder(DUMMY_NOTARY) database.transaction { - Cash.generateSpend(miniCorpServices, tx, amount, dest) + Cash.generateSpend(miniCorpServices, tx, amount, OUR_IDENTITY_AND_CERT, dest) } return tx.toWireTransaction(miniCorpServices) } @@ -565,7 +568,7 @@ class CashTests : TestDependencyInjectionBase() { @Test fun generateSimpleExit() { initialiseTestSerialization() - val wtx = makeExit(100.DOLLARS, MEGA_CORP, 1) + val wtx = makeExit(miniCorpServices, 100.DOLLARS, MEGA_CORP, 1) assertEquals(WALLET[0].ref, wtx.inputs[0]) assertEquals(0, wtx.outputs.size) @@ -581,10 +584,16 @@ class CashTests : TestDependencyInjectionBase() { @Test fun generatePartialExit() { initialiseTestSerialization() - val wtx = makeExit(50.DOLLARS, MEGA_CORP, 1) - assertEquals(WALLET[0].ref, wtx.inputs[0]) - assertEquals(1, wtx.outputs.size) - assertEquals(WALLET[0].state.data.copy(amount = WALLET[0].state.data.amount.splitEvenly(2).first()), wtx.getOutput(0)) + val wtx = makeExit(miniCorpServices, 50.DOLLARS, MEGA_CORP, 1) + val actualInput = wtx.inputs.single() + // Filter the available inputs and confirm exactly one has been used + val expectedInputs = WALLET.filter { it.ref == actualInput } + assertEquals(1, expectedInputs.size) + val inputState = expectedInputs.single() + val actualChange = wtx.outputs.single().data as Cash.State + val expectedChangeAmount = inputState.state.data.amount.quantity - 50.DOLLARS.quantity + val expectedChange = WALLET[0].state.data.copy(amount = WALLET[0].state.data.amount.copy(quantity = expectedChangeAmount), owner = actualChange.owner) + assertEquals(expectedChange, wtx.getOutput(0)) } /** @@ -593,7 +602,7 @@ class CashTests : TestDependencyInjectionBase() { @Test fun generateAbsentExit() { initialiseTestSerialization() - assertFailsWith { makeExit(100.POUNDS, MEGA_CORP, 1) } + assertFailsWith { makeExit(miniCorpServices, 100.POUNDS, MEGA_CORP, 1) } } /** @@ -602,7 +611,7 @@ class CashTests : TestDependencyInjectionBase() { @Test fun generateInvalidReferenceExit() { initialiseTestSerialization() - assertFailsWith { makeExit(100.POUNDS, MEGA_CORP, 2) } + assertFailsWith { makeExit(miniCorpServices, 100.POUNDS, MEGA_CORP, 2) } } /** @@ -611,7 +620,7 @@ class CashTests : TestDependencyInjectionBase() { @Test fun generateInsufficientExit() { initialiseTestSerialization() - assertFailsWith { makeExit(1000.DOLLARS, MEGA_CORP, 1) } + assertFailsWith { makeExit(miniCorpServices, 1000.DOLLARS, MEGA_CORP, 1) } } /** @@ -620,7 +629,7 @@ class CashTests : TestDependencyInjectionBase() { @Test fun generateOwnerWithNoStatesExit() { initialiseTestSerialization() - assertFailsWith { makeExit(100.POUNDS, CHARLIE, 1) } + assertFailsWith { makeExit(miniCorpServices, 100.POUNDS, CHARLIE, 1) } } /** @@ -629,9 +638,9 @@ class CashTests : TestDependencyInjectionBase() { @Test fun generateExitWithEmptyVault() { initialiseTestSerialization() - assertFailsWith { + assertFailsWith { val tx = TransactionBuilder(DUMMY_NOTARY) - Cash().generateExit(tx, Amount(100, Issued(CHARLIE.ref(1), GBP)), emptyList()) + Cash().generateExit(tx, Amount(100, Issued(CHARLIE.ref(1), GBP)), emptyList(), OUR_IDENTITY_1) } } @@ -643,7 +652,6 @@ class CashTests : TestDependencyInjectionBase() { makeSpend(100.DOLLARS, THEIR_IDENTITY_1) } database.transaction { - @Suppress("UNCHECKED_CAST") val vaultState = vaultStatesUnconsumed.elementAt(0) assertEquals(vaultState.ref, wtx.inputs[0]) assertEquals(vaultState.state.data.copy(owner = THEIR_IDENTITY_1), wtx.getOutput(0)) @@ -657,7 +665,7 @@ class CashTests : TestDependencyInjectionBase() { database.transaction { val tx = TransactionBuilder(DUMMY_NOTARY) - Cash.generateSpend(miniCorpServices, tx, 80.DOLLARS, ALICE, setOf(MINI_CORP)) + Cash.generateSpend(miniCorpServices, tx, 80.DOLLARS, OUR_IDENTITY_AND_CERT, ALICE, setOf(MINI_CORP)) assertEquals(vaultStatesUnconsumed.elementAt(2).ref, tx.inputStates()[0]) } @@ -671,16 +679,15 @@ class CashTests : TestDependencyInjectionBase() { makeSpend(10.DOLLARS, THEIR_IDENTITY_1) } database.transaction { - @Suppress("UNCHECKED_CAST") val vaultState = vaultStatesUnconsumed.elementAt(0) val changeAmount = 90.DOLLARS `issued by` defaultIssuer - val likelyChangeState = wtx.outputs.map(TransactionState<*>::data).filter { state -> + val likelyChangeState = wtx.outputs.map(TransactionState<*>::data).single { state -> if (state is Cash.State) { state.amount == changeAmount } else { false } - }.single() + } val changeOwner = (likelyChangeState as Cash.State).owner assertEquals(1, miniCorpServices.keyManagementService.filterMyKeys(setOf(changeOwner.owningKey)).toList().size) assertEquals(vaultState.ref, wtx.inputs[0]) @@ -698,7 +705,6 @@ class CashTests : TestDependencyInjectionBase() { makeSpend(500.DOLLARS, THEIR_IDENTITY_1) } database.transaction { - @Suppress("UNCHECKED_CAST") val vaultState0 = vaultStatesUnconsumed.elementAt(0) val vaultState1 = vaultStatesUnconsumed.elementAt(1) assertEquals(vaultState0.ref, wtx.inputs[0]) @@ -770,8 +776,8 @@ class CashTests : TestDependencyInjectionBase() { Cash.State(1000.POUNDS `issued by` MINI_CORP.ref(3), MEGA_CORP).amount.token) // States cannot be aggregated if the reference differs - assertNotEquals(fiveThousandDollarsFromMega.amount.token, (fiveThousandDollarsFromMega `with deposit` defaultIssuer).amount.token) - assertNotEquals((fiveThousandDollarsFromMega `with deposit` defaultIssuer).amount.token, fiveThousandDollarsFromMega.amount.token) + assertNotEquals(fiveThousandDollarsFromMega.amount.token, (fiveThousandDollarsFromMega withDeposit defaultIssuer).amount.token) + assertNotEquals((fiveThousandDollarsFromMega withDeposit defaultIssuer).amount.token, fiveThousandDollarsFromMega.amount.token) } @Test @@ -848,7 +854,7 @@ class CashTests : TestDependencyInjectionBase() { transaction { attachment(Cash.PROGRAM_ID) input("MEGA_CORP cash") - output(Cash.PROGRAM_ID, "MEGA_CORP cash 2", "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() } diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashExitFlowTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashExitFlowTests.kt similarity index 78% rename from perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashExitFlowTests.kt rename to perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashExitFlowTests.kt index 20e2b737cc..fc827ac9c6 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashExitFlowTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashExitFlowTests.kt @@ -1,22 +1,18 @@ -package com.r3.corda.enterprise.perftestcordapp.contracts.flows +package com.r3.corda.enterprise.perftestcordapp.flows -import net.corda.core.identity.Party -import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.getOrThrow -import com.r3.corda.enterprise.perftestcordapp.flows.PtCashException -import com.r3.corda.enterprise.perftestcordapp.flows.CashExitFlow -import com.r3.corda.enterprise.perftestcordapp.flows.CashIssueFlow import com.r3.corda.enterprise.perftestcordapp.DOLLARS import com.r3.corda.enterprise.perftestcordapp.`issued by` import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash +import net.corda.core.identity.Party +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode +import net.corda.testing.BOC import net.corda.testing.chooseIdentity 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 @@ -34,11 +30,10 @@ class CashExitFlowTests { @Before fun start() { - setCordappPackages("com.r3.corda.enterprise.perftestcordapp.contracts.asset") - mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin()) - val nodes = mockNet.createSomeNodes(1) - notaryNode = nodes.notaryNode - bankOfCordaNode = nodes.partyNodes[0] + mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset")) + notaryNode = mockNet.createNotaryNode() + bankOfCordaNode = mockNet.createPartyNode(BOC.name) + notary = notaryNode.services.getDefaultNotary() bankOfCorda = bankOfCordaNode.info.chooseIdentity() mockNet.runNetwork() @@ -51,7 +46,6 @@ class CashExitFlowTests { @After fun cleanUp() { mockNet.stopNodes() - unsetCordappPackages() } @Test @@ -72,7 +66,7 @@ class CashExitFlowTests { val expected = 0.DOLLARS val future = bankOfCordaNode.services.startFlow(CashExitFlow(expected, ref)).resultFuture mockNet.runNetwork() - assertFailsWith { + assertFailsWith { future.getOrThrow() } } diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashIssueFlowTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueFlowTests.kt similarity index 81% rename from perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashIssueFlowTests.kt rename to perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueFlowTests.kt index 3538535ba5..9df67f5f29 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashIssueFlowTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueFlowTests.kt @@ -1,4 +1,4 @@ -package com.r3.corda.enterprise.perftestcordapp.contracts.flows +package com.r3.corda.enterprise.perftestcordapp.flows import net.corda.core.identity.Party import net.corda.core.utilities.OpaqueBytes @@ -7,13 +7,12 @@ import com.r3.corda.enterprise.perftestcordapp.DOLLARS import com.r3.corda.enterprise.perftestcordapp.`issued by` import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash import net.corda.node.internal.StartedNode -import com.r3.corda.enterprise.perftestcordapp.flows.CashIssueFlow import net.corda.testing.chooseIdentity import net.corda.testing.getDefaultNotary +import net.corda.testing.BOC 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 @@ -29,15 +28,12 @@ class CashIssueFlowTests { @Before fun start() { - setCordappPackages("com.r3.corda.enterprise.perftestcordapp.contracts.asset") - mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin()) - val nodes = mockNet.createSomeNodes(1) - notaryNode = nodes.notaryNode - bankOfCordaNode = nodes.partyNodes[0] + mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset")) + notaryNode = mockNet.createNotaryNode() + bankOfCordaNode = mockNet.createPartyNode(BOC.name) bankOfCorda = bankOfCordaNode.info.chooseIdentity() - + notary = notaryNode.services.getDefaultNotary() mockNet.runNetwork() - notary = bankOfCordaNode.services.getDefaultNotary() } @After diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashPaymentFlowTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlowTests.kt similarity index 83% rename from perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashPaymentFlowTests.kt rename to perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlowTests.kt index ba3834947c..58aba54e43 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/CashPaymentFlowTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlowTests.kt @@ -1,26 +1,19 @@ -package com.r3.corda.enterprise.perftestcordapp.contracts.flows +package com.r3.corda.enterprise.perftestcordapp.flows +import com.r3.corda.enterprise.perftestcordapp.DOLLARS +import com.r3.corda.enterprise.perftestcordapp.`issued by` +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash import net.corda.core.identity.Party import net.corda.core.node.services.Vault import net.corda.core.node.services.trackBy import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow -import com.r3.corda.enterprise.perftestcordapp.DOLLARS -import com.r3.corda.enterprise.perftestcordapp.`issued by` -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash -import com.r3.corda.enterprise.perftestcordapp.flows.PtCashException -import com.r3.corda.enterprise.perftestcordapp.flows.CashIssueFlow -import com.r3.corda.enterprise.perftestcordapp.flows.CashPaymentFlow import net.corda.node.internal.StartedNode -import net.corda.testing.chooseIdentity -import net.corda.testing.expect -import net.corda.testing.expectEvents -import net.corda.testing.getDefaultNotary +import net.corda.testing.* 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 @@ -38,11 +31,9 @@ class CashPaymentFlowTests { @Before fun start() { - setCordappPackages("com.r3.corda.enterprise.perftestcordapp.contracts.asset") - mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin()) - val nodes = mockNet.createSomeNodes(1) - notaryNode = nodes.notaryNode - bankOfCordaNode = nodes.partyNodes[0] + mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset")) + notaryNode = mockNet.createNotaryNode() + bankOfCordaNode = mockNet.createPartyNode(BOC.name) bankOfCorda = bankOfCordaNode.info.chooseIdentity() notary = notaryNode.services.getDefaultNotary() val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, notary)).resultFuture @@ -64,8 +55,8 @@ class CashPaymentFlowTests { bankOfCordaNode.database.transaction { // Register for vault updates val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL) - val (_, vaultUpdatesBoc) = bankOfCordaNode.services.vaultQueryService.trackBy(criteria) - val (_, vaultUpdatesBankClient) = notaryNode.services.vaultQueryService.trackBy(criteria) + val (_, vaultUpdatesBoc) = bankOfCordaNode.services.vaultService.trackBy(criteria) + val (_, vaultUpdatesBankClient) = notaryNode.services.vaultService.trackBy(criteria) val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expectedPayment, payTo)).resultFuture @@ -102,7 +93,7 @@ class CashPaymentFlowTests { val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expected, payTo)).resultFuture mockNet.runNetwork() - assertFailsWith { + assertFailsWith { future.getOrThrow() } } diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/TwoPartyTradeFlowTest.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt similarity index 79% rename from perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/TwoPartyTradeFlowTest.kt rename to perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt index baf21c7eca..ee6fc4e0e4 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/flows/TwoPartyTradeFlowTest.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt @@ -1,4 +1,7 @@ -package com.r3.corda.enterprise.perftestcordapp.contracts.flows +package com.r3.corda.enterprise.perftestcordapp.flows + +// NB: Unlike the other flow tests in this package, this is not originally copied from net.corda.finance, but +// from net.corda.node.messaging import co.paralleluniverse.fibers.Suspendable import net.corda.core.concurrent.CordaFuture @@ -31,8 +34,6 @@ import com.r3.corda.enterprise.perftestcordapp.`issued by` import com.r3.corda.enterprise.perftestcordapp.contracts.CommercialPaper import com.r3.corda.enterprise.perftestcordapp.contracts.asset.CASH import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.`issued by` -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.`owned by` import com.r3.corda.enterprise.perftestcordapp.flows.TwoPartyTradeFlow.Buyer import com.r3.corda.enterprise.perftestcordapp.flows.TwoPartyTradeFlow.Seller import net.corda.node.internal.StartedNode @@ -43,13 +44,18 @@ import net.corda.node.utilities.CordaPersistence import net.corda.nodeapi.internal.ServiceInfo import net.corda.testing.* import com.r3.corda.enterprise.perftestcordapp.contracts.asset.fillWithSomeTestCash +import net.corda.node.services.api.Checkpoint +import net.corda.node.services.api.CheckpointStorage import net.corda.testing.node.InMemoryMessagingNetwork import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockServices import net.corda.testing.node.pumpReceive import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized import rx.Observable import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream @@ -62,18 +68,41 @@ import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertTrue + +/** + * Copied from DBCheckpointStorageTests as it is required as helper for this test + */ +internal fun CheckpointStorage.checkpoints(): List { + val checkpoints = mutableListOf() + forEach { + checkpoints += it + true + } + return checkpoints +} + + /** * In this example, Alice wishes to sell her commercial paper to Bob in return for $1,000,000 and they wish to do * it on the ledger atomically. Therefore they must work together to build a transaction. * * We assume that Alice and Bob already found each other via some market, and have agreed the details already. */ -class TwoPartyTradeFlowTests { +@RunWith(Parameterized::class) +class TwoPartyTradeFlowTests(val anonymous: Boolean) { + companion object { + private val cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts") + @JvmStatic + @Parameterized.Parameters + fun data(): Collection { + return listOf(true, false) + } + } + private lateinit var mockNet: MockNetwork @Before fun before() { - setCordappPackages("com.r3.corda.enterprise.perftestcordapp.contracts") LogHelper.setLevel("platform.trade", "core.contract.TransactionGroup", "recordingmap") } @@ -81,7 +110,6 @@ class TwoPartyTradeFlowTests { fun after() { mockNet.stopNodes() LogHelper.reset("platform.trade", "core.contract.TransactionGroup", "recordingmap") - unsetCordappPackages() } @Test @@ -89,18 +117,17 @@ class TwoPartyTradeFlowTests { // We run this in parallel threads to help catch any race conditions that may exist. The other tests // we run in the unit test thread exclusively to speed things up, ensure deterministic results and // allow interruption half way through. - mockNet = MockNetwork(false, true) - - ledger(initialiseSerialization = false) { - val basketOfNodes = mockNet.createSomeNodes(3) - val notaryNode = basketOfNodes.notaryNode - val aliceNode = basketOfNodes.partyNodes[0] - val bobNode = basketOfNodes.partyNodes[1] - val bankNode = basketOfNodes.partyNodes[2] - val cashIssuer = bankNode.info.chooseIdentity().ref(1) - val cpIssuer = bankNode.info.chooseIdentity().ref(1, 2, 3) - val notary = aliceNode.services.getDefaultNotary() - + mockNet = MockNetwork(false, true, cordappPackages = cordappPackages) + ledger(MockServices(cordappPackages), initialiseSerialization = false) { + val notaryNode = mockNet.createNotaryNode() + val aliceNode = mockNet.createPartyNode(ALICE_NAME) + val bobNode = mockNet.createPartyNode(BOB_NAME) + val bankNode = mockNet.createPartyNode(BOC_NAME) + val alice = aliceNode.info.singleIdentity() + val bank = bankNode.info.singleIdentity() + val notary = notaryNode.services.getDefaultNotary() + val cashIssuer = bank.ref(1) + val cpIssuer = bank.ref(1, 2, 3) aliceNode.internals.disableDBCloseOnStop() bobNode.internals.disableDBCloseOnStop() @@ -111,8 +138,8 @@ class TwoPartyTradeFlowTests { } val alicesFakePaper = aliceNode.database.transaction { - fillUpForSeller(false, cpIssuer, aliceNode.info.chooseIdentity(), - 1200.DOLLARS `issued by` bankNode.info.chooseIdentity().ref(0), null, notary).second + fillUpForSeller(false, cpIssuer, alice, + 1200.DOLLARS `issued by` bank.ref(0), null, notary).second } insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) @@ -127,27 +154,28 @@ class TwoPartyTradeFlowTests { aliceNode.dispose() bobNode.dispose() -// aliceNode.database.transaction { -// assertThat(aliceNode.checkpointStorage.checkpoints()).isEmpty() -// } + aliceNode.database.transaction { + assertThat(aliceNode.checkpointStorage.checkpoints()).isEmpty() + } aliceNode.internals.manuallyCloseDB() -// bobNode.database.transaction { -// assertThat(bobNode.checkpointStorage.checkpoints()).isEmpty() -// } + bobNode.database.transaction { + assertThat(bobNode.checkpointStorage.checkpoints()).isEmpty() + } bobNode.internals.manuallyCloseDB() } } @Test(expected = InsufficientBalanceException::class) fun `trade cash for commercial paper fails using soft locking`() { - mockNet = MockNetwork(false, true) - - ledger(initialiseSerialization = false) { - val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) - val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name) - val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name) - val bankNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOC.name) - val issuer = bankNode.info.chooseIdentity().ref(1) + mockNet = MockNetwork(false, true, cordappPackages = cordappPackages) + ledger(MockServices(cordappPackages), initialiseSerialization = false) { + val notaryNode = mockNet.createNotaryNode() + val aliceNode = mockNet.createPartyNode(ALICE_NAME) + val bobNode = mockNet.createPartyNode(BOB_NAME) + val bankNode = mockNet.createPartyNode(BOC_NAME) + val alice = aliceNode.info.singleIdentity() + val bank = bankNode.info.singleIdentity() + val issuer = bank.ref(1) val notary = aliceNode.services.getDefaultNotary() aliceNode.internals.disableDBCloseOnStop() @@ -159,8 +187,8 @@ class TwoPartyTradeFlowTests { } val alicesFakePaper = aliceNode.database.transaction { - fillUpForSeller(false, issuer, aliceNode.info.chooseIdentity(), - 1200.DOLLARS `issued by` bankNode.info.chooseIdentity().ref(0), null, notary).second + fillUpForSeller(false, issuer, alice, + 1200.DOLLARS `issued by` bank.ref(0), null, notary).second } insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) @@ -182,52 +210,43 @@ class TwoPartyTradeFlowTests { aliceNode.dispose() bobNode.dispose() -// aliceNode.database.transaction { -// assertThat(aliceNode.checkpointStorage.checkpoints()).isEmpty() -// } + aliceNode.database.transaction { + assertThat(aliceNode.checkpointStorage.checkpoints()).isEmpty() + } aliceNode.internals.manuallyCloseDB() -// bobNode.database.transaction { -// assertThat(bobNode.checkpointStorage.checkpoints()).isEmpty() -// } + bobNode.database.transaction { + assertThat(bobNode.checkpointStorage.checkpoints()).isEmpty() + } bobNode.internals.manuallyCloseDB() } } @Test fun `shutdown and restore`() { - mockNet = MockNetwork(false) - ledger(initialiseSerialization = false) { - val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) - val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name) - var bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name) - val bankNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOC.name) - val issuer = bankNode.info.chooseIdentity().ref(1, 2, 3) - - // Let the nodes know about each other - normally the network map would handle this - mockNet.registerIdentities() - - aliceNode.database.transaction { - aliceNode.services.identityService.verifyAndRegisterIdentity(bobNode.info.chooseIdentityAndCert()) - } - bobNode.database.transaction { - bobNode.services.identityService.verifyAndRegisterIdentity(aliceNode.info.chooseIdentityAndCert()) - } + mockNet = MockNetwork(false, cordappPackages = cordappPackages) + ledger(MockServices(cordappPackages), initialiseSerialization = false) { + val notaryNode = mockNet.createNotaryNode() + val aliceNode = mockNet.createPartyNode(ALICE_NAME) + var bobNode = mockNet.createPartyNode(BOB_NAME) + val bankNode = mockNet.createPartyNode(BOC_NAME) aliceNode.internals.disableDBCloseOnStop() bobNode.internals.disableDBCloseOnStop() val bobAddr = bobNode.network.myAddress as InMemoryMessagingNetwork.PeerHandle - val networkMapAddress = notaryNode.network.myAddress - mockNet.runNetwork() // Clear network map registration messages - val notary = aliceNode.services.getDefaultNotary() + + val notary = notaryNode.services.getDefaultNotary() + val alice = aliceNode.info.singleIdentity() + val bank = bankNode.info.singleIdentity() + val issuer = bank.ref(1, 2, 3) bobNode.database.transaction { bobNode.services.fillWithSomeTestCash(2000.DOLLARS, bankNode.services, outputNotary = notary, issuedBy = issuer) } val alicesFakePaper = aliceNode.database.transaction { - fillUpForSeller(false, issuer, aliceNode.info.chooseIdentity(), - 1200.DOLLARS `issued by` bankNode.info.chooseIdentity().ref(0), null, notary).second + fillUpForSeller(false, issuer, alice, + 1200.DOLLARS `issued by` bank.ref(0), null, notary).second } insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) val aliceFuture = runBuyerAndSeller(notary, aliceNode, bobNode, "alice's paper".outputStateAndRef()).sellerResult @@ -244,10 +263,10 @@ class TwoPartyTradeFlowTests { aliceNode.pumpReceive() bobNode.pumpReceive() -// // OK, now Bob has sent the partial transaction back to Alice and is waiting for Alice's signature. -// bobNode.database.transaction { -// assertThat(bobNode.checkpointStorage.checkpoints()).hasSize(1) -// } + // OK, now Bob has sent the partial transaction back to Alice and is waiting for Alice's signature. + bobNode.database.transaction { + assertThat(bobNode.checkpointStorage.checkpoints()).hasSize(1) + } val storage = bobNode.services.validatedTransactions val bobTransactionsBeforeCrash = bobNode.database.transaction { @@ -267,13 +286,12 @@ class TwoPartyTradeFlowTests { // ... bring the node back up ... the act of constructing the SMM will re-register the message handlers // that Bob was waiting on before the reboot occurred. - bobNode = mockNet.createNode(networkMapAddress, bobAddr.id, object : MockNetwork.Factory { + bobNode = mockNet.createNode(bobAddr.id, object : MockNetwork.Factory { override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - advertisedServices: Set, id: Int, overrideServices: Map?, - entropyRoot: BigInteger): MockNetwork.MockNode { - return MockNetwork.MockNode(config, network, networkMapAddr, advertisedServices, bobAddr.id, overrideServices, entropyRoot) + id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNetwork.MockNode { + return MockNetwork.MockNode(config, network, networkMapAddr, bobAddr.id, notaryIdentity, entropyRoot) } - }, BOB.name) + }, BOB_NAME) // Find the future representing the result of this state machine again. val bobFuture = bobNode.smm.findStateMachines(BuyerAcceptor::class.java).single().second @@ -285,12 +303,12 @@ class TwoPartyTradeFlowTests { assertThat(bobFuture.getOrThrow()).isEqualTo(aliceFuture.getOrThrow()) assertThat(bobNode.smm.findStateMachines(Buyer::class.java)).isEmpty() -// bobNode.database.transaction { -// assertThat(bobNode.checkpointStorage.checkpoints()).isEmpty() -// } -// aliceNode.database.transaction { -// assertThat(aliceNode.checkpointStorage.checkpoints()).isEmpty() -// } + bobNode.database.transaction { + assertThat(bobNode.checkpointStorage.checkpoints()).isEmpty() + } + aliceNode.database.transaction { + assertThat(aliceNode.checkpointStorage.checkpoints()).isEmpty() + } bobNode.database.transaction { val restoredBobTransactions = bobTransactionsBeforeCrash.filter { @@ -306,18 +324,15 @@ class TwoPartyTradeFlowTests { // Creates a mock node with an overridden storage service that uses a RecordingMap, that lets us test the order // of gets and puts. - private fun makeNodeWithTracking( - networkMapAddress: SingleMessageRecipient?, - name: CordaX500Name): StartedNode { + private fun makeNodeWithTracking(name: CordaX500Name): StartedNode { // Create a node in the mock network ... - return mockNet.createNode(networkMapAddress, nodeFactory = object : MockNetwork.Factory { + return mockNet.createNode(nodeFactory = object : MockNetwork.Factory { override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - advertisedServices: Set, id: Int, - overrideServices: Map?, + id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNetwork.MockNode { - return object : MockNetwork.MockNode(config, network, networkMapAddr, advertisedServices, id, overrideServices, entropyRoot) { + return object : MockNetwork.MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) { // That constructs a recording tx storage override fun makeTransactionStorage(): WritableTransactionStorage { return RecordingTransactionStorage(database, super.makeTransactionStorage()) @@ -329,18 +344,18 @@ class TwoPartyTradeFlowTests { @Test fun `check dependencies of sale asset are resolved`() { - mockNet = MockNetwork(false) - - val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) - val aliceNode = makeNodeWithTracking(notaryNode.network.myAddress, ALICE.name) - val bobNode = makeNodeWithTracking(notaryNode.network.myAddress, BOB.name) - val bankNode = makeNodeWithTracking(notaryNode.network.myAddress, BOC.name) - val issuer = bankNode.info.chooseIdentity().ref(1, 2, 3) + mockNet = MockNetwork(false, cordappPackages = cordappPackages) + val notaryNode = mockNet.createNotaryNode() + val aliceNode = makeNodeWithTracking(ALICE_NAME) + val bobNode = makeNodeWithTracking(BOB_NAME) + val bankNode = makeNodeWithTracking(BOC_NAME) mockNet.runNetwork() notaryNode.internals.ensureRegistered() val notary = aliceNode.services.getDefaultNotary() - - mockNet.registerIdentities() + val alice = aliceNode.info.singleIdentity() + val bob = bobNode.info.singleIdentity() + val bank = bankNode.info.singleIdentity() + val issuer = bank.ref(1, 2, 3) ledger(aliceNode.services, initialiseSerialization = false) { @@ -356,12 +371,12 @@ class TwoPartyTradeFlowTests { } val bobsFakeCash = bobNode.database.transaction { - fillUpForBuyer(false, issuer, AnonymousParty(bobNode.info.chooseIdentity().owningKey), notary) + fillUpForBuyer(false, issuer, AnonymousParty(bob.owningKey), notary) }.second val bobsSignedTxns = insertFakeTransactions(bobsFakeCash, bobNode, notaryNode, bankNode) val alicesFakePaper = aliceNode.database.transaction { - fillUpForSeller(false, issuer, aliceNode.info.chooseIdentity(), - 1200.DOLLARS `issued by` bankNode.info.chooseIdentity().ref(0), attachmentID, notary).second + fillUpForSeller(false, issuer, alice, + 1200.DOLLARS `issued by` bank.ref(0), attachmentID, notary).second } val alicesSignedTxns = insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) @@ -436,19 +451,18 @@ class TwoPartyTradeFlowTests { @Test fun `track works`() { - mockNet = MockNetwork(false) - - val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) - val aliceNode = makeNodeWithTracking(notaryNode.network.myAddress, ALICE.name) - val bobNode = makeNodeWithTracking(notaryNode.network.myAddress, BOB.name) - val bankNode = makeNodeWithTracking(notaryNode.network.myAddress, BOC.name) - val issuer = bankNode.info.chooseIdentity().ref(1, 2, 3) + mockNet = MockNetwork(false, cordappPackages = cordappPackages) + val notaryNode = mockNet.createNotaryNode() + val aliceNode = makeNodeWithTracking(ALICE_NAME) + val bobNode = makeNodeWithTracking(BOB_NAME) + val bankNode = makeNodeWithTracking(BOC_NAME) mockNet.runNetwork() notaryNode.internals.ensureRegistered() val notary = aliceNode.services.getDefaultNotary() - - mockNet.registerIdentities() + val alice: Party = aliceNode.info.singleIdentity() + val bank: Party = bankNode.info.singleIdentity() + val issuer = bank.ref(1, 2, 3) ledger(aliceNode.services, initialiseSerialization = false) { // Insert a prospectus type attachment into the commercial paper transaction. @@ -469,8 +483,8 @@ class TwoPartyTradeFlowTests { insertFakeTransactions(bobsFakeCash, bobNode, notaryNode, bankNode) val alicesFakePaper = aliceNode.database.transaction { - fillUpForSeller(false, issuer, aliceNode.info.chooseIdentity(), - 1200.DOLLARS `issued by` bankNode.info.chooseIdentity().ref(0), attachmentID, notary).second + fillUpForSeller(false, issuer, alice, + 1200.DOLLARS `issued by` bank.ref(0), attachmentID, notary).second } insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, bankNode) @@ -519,16 +533,16 @@ class TwoPartyTradeFlowTests { @Test fun `dependency with error on buyer side`() { - mockNet = MockNetwork(false) - ledger(initialiseSerialization = false) { + mockNet = MockNetwork(false, cordappPackages = cordappPackages) + ledger(MockServices(cordappPackages), initialiseSerialization = false) { runWithError(true, false, "at least one cash input") } } @Test fun `dependency with error on seller side`() { - mockNet = MockNetwork(false) - ledger(initialiseSerialization = false) { + mockNet = MockNetwork(false, cordappPackages = cordappPackages) + ledger(MockServices(cordappPackages), initialiseSerialization = false) { runWithError(false, true, "Issuances have a time-window") } } @@ -543,8 +557,7 @@ class TwoPartyTradeFlowTests { private fun runBuyerAndSeller(notary: Party, sellerNode: StartedNode, buyerNode: StartedNode, - assetToSell: StateAndRef, - anonymous: Boolean = true): RunResult { + assetToSell: StateAndRef): RunResult { val buyerFlows: Observable> = buyerNode.internals.registerInitiatedFlow(BuyerAcceptor::class.java) val firstBuyerFiber = buyerFlows.toFuture().map { it.stateMachine } val seller = SellerInitiator(buyerNode.info.chooseIdentity(), notary, assetToSell, 1000.DOLLARS, anonymous) @@ -595,26 +608,24 @@ class TwoPartyTradeFlowTests { aliceError: Boolean, expectedMessageSubstring: String ) { - val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name) - val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name) - val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name) - val bankNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOC.name) - val issuer = bankNode.info.chooseIdentity().ref(1, 2, 3) + val notaryNode = mockNet.createNotaryNode() + val aliceNode = mockNet.createPartyNode(ALICE_NAME) + val bobNode = mockNet.createPartyNode(BOB_NAME) + val bankNode = mockNet.createPartyNode(BOC_NAME) mockNet.runNetwork() notaryNode.internals.ensureRegistered() val notary = aliceNode.services.getDefaultNotary() - - // Let the nodes know about each other - normally the network map would handle this - mockNet.registerIdentities() + val alice = aliceNode.info.singleIdentity() + val bob = bobNode.info.singleIdentity() + val bank = bankNode.info.singleIdentity() + val issuer = bank.ref(1, 2, 3) val bobsBadCash = bobNode.database.transaction { - fillUpForBuyer(bobError, issuer, bobNode.info.chooseIdentity(), - notary).second + fillUpForBuyer(bobError, issuer, bob, notary).second } val alicesFakePaper = aliceNode.database.transaction { - fillUpForSeller(aliceError, issuer, aliceNode.info.chooseIdentity(), - 1200.DOLLARS `issued by` issuer, null, notary).second + fillUpForSeller(aliceError, issuer, alice,1200.DOLLARS `issued by` issuer, null, notary).second } insertFakeTransactions(bobsBadCash, bobNode, notaryNode, bankNode) @@ -680,8 +691,8 @@ class TwoPartyTradeFlowTests { // wants to sell to Bob. val eb1 = transaction(transactionBuilder = TransactionBuilder(notary = notary)) { // Issued money to itself. - output(Cash.PROGRAM_ID, "elbonian money 1", notary = notary) { 800.DOLLARS.CASH `issued by` issuer `owned by` interimOwner } - output(Cash.PROGRAM_ID, "elbonian money 2", notary = notary) { 1000.DOLLARS.CASH `issued by` issuer `owned by` interimOwner } + output(Cash.PROGRAM_ID, "elbonian money 1", notary = notary) { 800.DOLLARS.CASH issuedBy issuer ownedBy interimOwner } + output(Cash.PROGRAM_ID, "elbonian money 2", notary = notary) { 1000.DOLLARS.CASH issuedBy issuer ownedBy interimOwner } if (!withError) { command(issuer.party.owningKey) { Cash.Commands.Issue() } } else { @@ -699,15 +710,15 @@ class TwoPartyTradeFlowTests { // Bob gets some cash onto the ledger from BoE val bc1 = transaction(transactionBuilder = TransactionBuilder(notary = notary)) { input("elbonian money 1") - output(Cash.PROGRAM_ID, "bob cash 1", notary = notary) { 800.DOLLARS.CASH `issued by` issuer `owned by` owner } + output(Cash.PROGRAM_ID, "bob cash 1", notary = notary) { 800.DOLLARS.CASH issuedBy issuer ownedBy owner } command(interimOwner.owningKey) { Cash.Commands.Move() } this.verifies() } val bc2 = transaction(transactionBuilder = TransactionBuilder(notary = notary)) { input("elbonian money 2") - output(Cash.PROGRAM_ID, "bob cash 2", notary = notary) { 300.DOLLARS.CASH `issued by` issuer `owned by` owner } - output(Cash.PROGRAM_ID, notary = notary) { 700.DOLLARS.CASH `issued by` issuer `owned by` interimOwner } // Change output. + output(Cash.PROGRAM_ID, "bob cash 2", notary = notary) { 300.DOLLARS.CASH issuedBy issuer ownedBy owner } + output(Cash.PROGRAM_ID, notary = notary) { 700.DOLLARS.CASH issuedBy issuer ownedBy interimOwner } // Change output. command(interimOwner.owningKey) { Cash.Commands.Move() } this.verifies() } From 3297be4a07dfb47a3e43db6dd60be8acd787dcb5 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Thu, 19 Oct 2017 10:43:15 +0100 Subject: [PATCH 14/15] Clean up imports --- .../contracts/asset/cash/selection/CashSelectionH2Impl.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/CashSelectionH2Impl.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/CashSelectionH2Impl.kt index c79737f531..557d729abb 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/CashSelectionH2Impl.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/cash/selection/CashSelectionH2Impl.kt @@ -4,7 +4,6 @@ import net.corda.core.contracts.Amount import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.utilities.* -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.cash.selection.AbstractCashSelection import java.sql.Connection import java.sql.DatabaseMetaData import java.sql.ResultSet From 35ef7d19da1bb586f3c752a599949a1ac6ed1ec5 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Thu, 19 Oct 2017 10:44:20 +0100 Subject: [PATCH 15/15] Merged fix for TwoPartyTradeFlow not finding the seller identity --- .../enterprise/perftestcordapp/flows/TwoPartyTradeFlow.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlow.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlow.kt index a45ead941c..7c5c615ef2 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlow.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlow.kt @@ -202,8 +202,8 @@ object TwoPartyTradeFlow { // Register the identity we're about to send payment to. This shouldn't be the same as the asset owner // identity, so that anonymity is enforced. - val wellKnownPayToIdentity = serviceHub.identityService.verifyAndRegisterIdentity(it.payToIdentity) - require(wellKnownPayToIdentity?.party == sellerSession.counterparty) { "Well known identity to pay to must match counterparty identity" } + val wellKnownPayToIdentity = serviceHub.identityService.verifyAndRegisterIdentity(it.payToIdentity) ?: it.payToIdentity + require(wellKnownPayToIdentity.party == sellerSession.counterparty) { "Well known identity to pay to must match counterparty identity" } if (it.price > acceptablePrice) throw UnacceptablePriceException(it.price)