CORDA-604: Update cash spending to handle multiple identities (#1534)

Update cash spending to handle multiple identities per node. For test cases nodes typically have a single identity, which we extract using `chooseIdentity()`, however for production environments we need to support nodes having multiple identities they can represent, with none being special.
This commit is contained in:
Ross Nicoll 2017-09-25 22:00:24 +01:00 committed by GitHub
parent 0a9e4f40fe
commit 23f16b4b25
7 changed files with 94 additions and 19 deletions

View File

@ -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<State> 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<State> 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()));
}

View File

@ -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<State>, services: ServiceHub) {
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, 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)
}

View File

@ -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<Currency, Cash.Commands, Cash.State>() {
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<Currency, Cash.Commands, Cash.State>() {
* @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<Currency>,
to: AbstractParty,
onlyFromParties: Set<AbstractParty> = 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<Currency, Cash.Commands, Cash.State>() {
fun generateSpend(services: ServiceHub,
tx: TransactionBuilder,
amount: Amount<Currency>,
ourIdentity: PartyAndCertificate,
to: AbstractParty,
onlyFromParties: Set<AbstractParty> = emptySet()): Pair<TransactionBuilder, List<PublicKey>> {
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<PartyAndAmount<Currency>>,
onlyFromParties: Set<AbstractParty> = 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<Currency, Cash.Commands, Cash.State>() {
fun generateSpend(services: ServiceHub,
tx: TransactionBuilder,
payments: List<PartyAndAmount<Currency>>,
ourIdentity: PartyAndCertificate,
onlyFromParties: Set<AbstractParty> = emptySet()): Pair<TransactionBuilder, List<PublicKey>> {
fun deriveState(txState: TransactionState<Cash.State>, amt: Amount<Issued<Currency>>, owner: AbstractParty)
= txState.copy(data = txState.data.copy(amount = amt, owner = owner))
@ -330,7 +390,7 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
// 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) },

View File

@ -51,6 +51,7 @@ open class CashPaymentFlow(
Cash.generateSpend(serviceHub,
builder,
amount,
ourIdentity,
anonymousRecipient,
issuerConstraint)
} catch (e: InsufficientBalanceException) {

View File

@ -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)

View File

@ -290,7 +290,7 @@ class CommercialPaperTestsGeneric {
fun makeRedeemTX(time: Instant): Pair<SignedTransaction, UUID> {
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)

View File

@ -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<StateAndRef<LinearState>>,
fun ServiceHub.evolveLinearStates(linearStates: List<StateAndRef<LinearState>>, notary: Party) = consumeAndProduce(linearStates, notary)
fun ServiceHub.evolveLinearState(linearState: StateAndRef<LinearState>, notary: Party) : StateAndRef<LinearState> = 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<Currency>, to: Party = CHARLIE, notary: Party): Vault.Update<ContractState> {
return consumeCash(amount, myInfo.chooseIdentityAndCert(), to, notary)
}
/**
* Consume cash, sending any change to the specified identity.
*/
@JvmOverloads
fun ServiceHub.consumeCash(amount: Amount<Currency>, ourIdentity: PartyAndCertificate, to: Party = CHARLIE, notary: Party): Vault.Update<ContractState> {
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)