diff --git a/finance/src/main/java/net/corda/finance/contracts/JavaCommercialPaper.java b/finance/src/main/java/net/corda/finance/contracts/JavaCommercialPaper.java index 3838fb4feb..468d1919c8 100644 --- a/finance/src/main/java/net/corda/finance/contracts/JavaCommercialPaper.java +++ b/finance/src/main/java/net/corda/finance/contracts/JavaCommercialPaper.java @@ -7,6 +7,7 @@ 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; @@ -239,8 +240,11 @@ public class JavaCommercialPaper implements Contract { } @Suspendable - public void generateRedeem(TransactionBuilder tx, StateAndRef paper, ServiceHub services) throws InsufficientBalanceException { - Cash.generateSpend(services, tx, Structures.withoutIssuer(paper.getState().getData().getFaceValue()), paper.getState().getData().getOwner(), Collections.emptySet()); + public void generateRedeem(final TransactionBuilder tx, + final StateAndRef paper, + final ServiceHub services, + final PartyAndCertificate ourIdentity) throws InsufficientBalanceException { + Cash.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())); } diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/CommercialPaper.kt b/finance/src/main/kotlin/net/corda/finance/contracts/CommercialPaper.kt index 592456a84f..f7fcdc3e69 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/CommercialPaper.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/CommercialPaper.kt @@ -6,6 +6,7 @@ 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 @@ -186,9 +187,9 @@ class CommercialPaper : Contract { */ @Throws(InsufficientBalanceException::class) @Suspendable - fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef, services: ServiceHub) { + fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef, services: ServiceHub, ourIdentity: PartyAndCertificate) { // Add the cash movement using the states in our vault. - Cash.generateSpend(services, tx, paper.state.data.faceValue.withoutIssuer(), 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/finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt index 0b405647b2..ba595d1672 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt @@ -11,6 +11,7 @@ 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 @@ -266,7 +267,8 @@ class Cash : OnLedgerAsset() { companion object { /** - * Generate a transaction that moves an amount of currency to the given pubkey. + * 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] * @@ -274,7 +276,36 @@ class Cash : OnLedgerAsset() { * @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 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. @@ -289,21 +320,49 @@ class Cash : OnLedgerAsset() { fun generateSpend(services: ServiceHub, tx: TransactionBuilder, amount: Amount, + ourIdentity: PartyAndCertificate, to: AbstractParty, onlyFromParties: Set = emptySet()): Pair> { - return generateSpend(services, tx, listOf(PartyAndAmount(to, amount)), onlyFromParties) + return generateSpend(services, tx, listOf(PartyAndAmount(to, amount)), ourIdentity, onlyFromParties) } /** - * Generate a transaction that moves an amount of currency to the given pubkey. + * 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 amount How much currency to send. - * @param to a key of the recipient. + * @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. @@ -318,6 +377,7 @@ class Cash : OnLedgerAsset() { 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)) @@ -330,7 +390,7 @@ class Cash : OnLedgerAsset() { // 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(services.myInfo.legalIdentitiesAndCerts.first(), revocationEnabled) + val changeIdentity = services.keyManagementService.freshKeyAndCert(ourIdentity, revocationEnabled) return OnLedgerAsset.generateSpend(tx, payments, acceptableCoins, changeIdentity.party.anonymise(), { state, quantity, owner -> deriveState(state, quantity, owner) }, diff --git a/finance/src/main/kotlin/net/corda/finance/flows/CashPaymentFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/CashPaymentFlow.kt index 8f6402a799..49418436c3 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/CashPaymentFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/CashPaymentFlow.kt @@ -51,6 +51,7 @@ open class CashPaymentFlow( Cash.generateSpend(serviceHub, builder, amount, + ourIdentity, anonymousRecipient, issuerConstraint) } catch (e: InsufficientBalanceException) { diff --git a/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt index d8521f19ed..75ef987fb1 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt @@ -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) = Cash.generateSpend(serviceHub, ptx, tradeRequest.price, tradeRequest.payToIdentity.party) + val (tx, cashSigningPubKeys) = Cash.generateSpend(serviceHub, ptx, tradeRequest.price, ourIdentity, tradeRequest.payToIdentity.party) // Add inputs/outputs/a command for the movement of the asset. tx.addInputState(assetForSale) diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt b/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt index 0570b327c5..71b25ec522 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt @@ -290,7 +290,7 @@ class CommercialPaperTestsGeneric { fun makeRedeemTX(time: Instant): Pair { val builder = TransactionBuilder(DUMMY_NOTARY) builder.setTimeWindow(time, 30.seconds) - CommercialPaper().generateRedeem(builder, moveTX.tx.outRef(1), bigCorpServices) + 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/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/VaultFiller.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/VaultFiller.kt index 1cf2128554..8abe70d329 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/VaultFiller.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/VaultFiller.kt @@ -8,6 +8,7 @@ import net.corda.core.crypto.SignatureMetadata 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.node.services.Vault import net.corda.core.toFuture @@ -21,11 +22,7 @@ import net.corda.finance.contracts.asset.Cash import net.corda.finance.contracts.asset.CommodityContract import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER import net.corda.finance.contracts.asset.DUMMY_OBLIGATION_ISSUER -import net.corda.testing.CHARLIE -import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.DUMMY_NOTARY_KEY -import net.corda.testing.dummyCommand -import net.corda.testing.chooseIdentity +import net.corda.testing.* import java.security.PublicKey import java.time.Duration import java.time.Instant @@ -243,14 +240,26 @@ fun ServiceHub.consumeLinearStates(linearStates: List>, fun ServiceHub.evolveLinearStates(linearStates: List>, notary: Party) = consumeAndProduce(linearStates, notary) fun ServiceHub.evolveLinearState(linearState: StateAndRef, notary: Party) : StateAndRef = consumeAndProduce(linearState, notary) +/** + * Consume cash, sending any change to the default identity for this node. Only suitable for use in test scenarios, + * where nodes have a default identity. + */ @JvmOverloads fun ServiceHub.consumeCash(amount: Amount, to: Party = CHARLIE, notary: Party): Vault.Update { + return consumeCash(amount, myInfo.chooseIdentityAndCert(), to, notary) +} + +/** + * Consume cash, sending any change to the specified identity. + */ +@JvmOverloads +fun ServiceHub.consumeCash(amount: Amount, ourIdentity: PartyAndCertificate, to: Party = CHARLIE, notary: Party): Vault.Update { val update = vaultService.rawUpdates.toFuture() val services = this // A tx that spends our money. val builder = TransactionBuilder(notary).apply { - Cash.generateSpend(services, this, amount, to) + Cash.generateSpend(services, this, amount, ourIdentity, to) } val spendTx = signInitialTransaction(builder, notary.owningKey)