mirror of
https://github.com/corda/corda.git
synced 2025-06-01 23:20:54 +00:00
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:
parent
0a9e4f40fe
commit
23f16b4b25
@ -7,6 +7,7 @@ import net.corda.core.crypto.NullKeys.NullPublicKey;
|
|||||||
import net.corda.core.identity.AbstractParty;
|
import net.corda.core.identity.AbstractParty;
|
||||||
import net.corda.core.identity.AnonymousParty;
|
import net.corda.core.identity.AnonymousParty;
|
||||||
import net.corda.core.identity.Party;
|
import net.corda.core.identity.Party;
|
||||||
|
import net.corda.core.identity.PartyAndCertificate;
|
||||||
import net.corda.core.node.ServiceHub;
|
import net.corda.core.node.ServiceHub;
|
||||||
import net.corda.core.transactions.LedgerTransaction;
|
import net.corda.core.transactions.LedgerTransaction;
|
||||||
import net.corda.core.transactions.TransactionBuilder;
|
import net.corda.core.transactions.TransactionBuilder;
|
||||||
@ -239,8 +240,11 @@ public class JavaCommercialPaper implements Contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
public void generateRedeem(TransactionBuilder tx, StateAndRef<State> paper, ServiceHub services) throws InsufficientBalanceException {
|
public void generateRedeem(final TransactionBuilder tx,
|
||||||
Cash.generateSpend(services, tx, Structures.withoutIssuer(paper.getState().getData().getFaceValue()), paper.getState().getData().getOwner(), Collections.emptySet());
|
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.addInputState(paper);
|
||||||
tx.addCommand(new Command<>(new Commands.Redeem(), paper.getState().getData().getOwner().getOwningKey()));
|
tx.addCommand(new Command<>(new Commands.Redeem(), paper.getState().getData().getOwner().getOwningKey()));
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import net.corda.core.crypto.NullKeys.NULL_PARTY
|
|||||||
import net.corda.core.utilities.toBase58String
|
import net.corda.core.utilities.toBase58String
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.identity.PartyAndCertificate
|
||||||
import net.corda.core.internal.Emoji
|
import net.corda.core.internal.Emoji
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.schemas.MappedSchema
|
import net.corda.core.schemas.MappedSchema
|
||||||
@ -186,9 +187,9 @@ class CommercialPaper : Contract {
|
|||||||
*/
|
*/
|
||||||
@Throws(InsufficientBalanceException::class)
|
@Throws(InsufficientBalanceException::class)
|
||||||
@Suspendable
|
@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.
|
// 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.addInputState(paper)
|
||||||
tx.addCommand(Commands.Redeem(), paper.state.data.owner.owningKey)
|
tx.addCommand(Commands.Redeem(), paper.state.data.owner.owningKey)
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import net.corda.core.crypto.entropyToKeyPair
|
|||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.identity.PartyAndCertificate
|
||||||
import net.corda.core.internal.Emoji
|
import net.corda.core.internal.Emoji
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.schemas.MappedSchema
|
import net.corda.core.schemas.MappedSchema
|
||||||
@ -266,7 +267,8 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
|
|||||||
|
|
||||||
companion object {
|
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]
|
* 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
|
* @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.
|
* to move the cash will be added on top.
|
||||||
* @param amount How much currency to send.
|
* @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
|
* @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
|
* 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.
|
* 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,
|
fun generateSpend(services: ServiceHub,
|
||||||
tx: TransactionBuilder,
|
tx: TransactionBuilder,
|
||||||
amount: Amount<Currency>,
|
amount: Amount<Currency>,
|
||||||
|
ourIdentity: PartyAndCertificate,
|
||||||
to: AbstractParty,
|
to: AbstractParty,
|
||||||
onlyFromParties: Set<AbstractParty> = emptySet()): Pair<TransactionBuilder, List<PublicKey>> {
|
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]
|
* 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 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
|
* @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.
|
* to move the cash will be added on top.
|
||||||
* @param amount How much currency to send.
|
* @param payments A list of amounts to pay, and the party to send the payment to.
|
||||||
* @param to a key of the recipient.
|
* @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
|
* @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
|
* 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.
|
* 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,
|
fun generateSpend(services: ServiceHub,
|
||||||
tx: TransactionBuilder,
|
tx: TransactionBuilder,
|
||||||
payments: List<PartyAndAmount<Currency>>,
|
payments: List<PartyAndAmount<Currency>>,
|
||||||
|
ourIdentity: PartyAndCertificate,
|
||||||
onlyFromParties: Set<AbstractParty> = emptySet()): Pair<TransactionBuilder, List<PublicKey>> {
|
onlyFromParties: Set<AbstractParty> = emptySet()): Pair<TransactionBuilder, List<PublicKey>> {
|
||||||
fun deriveState(txState: TransactionState<Cash.State>, amt: Amount<Issued<Currency>>, owner: AbstractParty)
|
fun deriveState(txState: TransactionState<Cash.State>, amt: Amount<Issued<Currency>>, owner: AbstractParty)
|
||||||
= txState.copy(data = txState.data.copy(amount = amt, owner = owner))
|
= 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
|
// 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
|
// third party with a copy of the transaction (such as the notary) cannot identify who the change was
|
||||||
// sent to
|
// 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,
|
return OnLedgerAsset.generateSpend(tx, payments, acceptableCoins,
|
||||||
changeIdentity.party.anonymise(),
|
changeIdentity.party.anonymise(),
|
||||||
{ state, quantity, owner -> deriveState(state, quantity, owner) },
|
{ state, quantity, owner -> deriveState(state, quantity, owner) },
|
||||||
|
@ -51,6 +51,7 @@ open class CashPaymentFlow(
|
|||||||
Cash.generateSpend(serviceHub,
|
Cash.generateSpend(serviceHub,
|
||||||
builder,
|
builder,
|
||||||
amount,
|
amount,
|
||||||
|
ourIdentity,
|
||||||
anonymousRecipient,
|
anonymousRecipient,
|
||||||
issuerConstraint)
|
issuerConstraint)
|
||||||
} catch (e: InsufficientBalanceException) {
|
} catch (e: InsufficientBalanceException) {
|
||||||
|
@ -219,7 +219,7 @@ object TwoPartyTradeFlow {
|
|||||||
val ptx = TransactionBuilder(notary)
|
val ptx = TransactionBuilder(notary)
|
||||||
|
|
||||||
// Add input and output states for the movement of cash, by using the Cash contract to generate the states
|
// 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.
|
// Add inputs/outputs/a command for the movement of the asset.
|
||||||
tx.addInputState(assetForSale)
|
tx.addInputState(assetForSale)
|
||||||
|
@ -290,7 +290,7 @@ class CommercialPaperTestsGeneric {
|
|||||||
fun makeRedeemTX(time: Instant): Pair<SignedTransaction, UUID> {
|
fun makeRedeemTX(time: Instant): Pair<SignedTransaction, UUID> {
|
||||||
val builder = TransactionBuilder(DUMMY_NOTARY)
|
val builder = TransactionBuilder(DUMMY_NOTARY)
|
||||||
builder.setTimeWindow(time, 30.seconds)
|
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 ptx = aliceServices.signInitialTransaction(builder)
|
||||||
val ptx2 = bigCorpServices.addSignature(ptx)
|
val ptx2 = bigCorpServices.addSignature(ptx)
|
||||||
val stx = notaryServices.addSignature(ptx2)
|
val stx = notaryServices.addSignature(ptx2)
|
||||||
|
@ -8,6 +8,7 @@ import net.corda.core.crypto.SignatureMetadata
|
|||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.AnonymousParty
|
import net.corda.core.identity.AnonymousParty
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.identity.PartyAndCertificate
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.services.Vault
|
import net.corda.core.node.services.Vault
|
||||||
import net.corda.core.toFuture
|
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.CommodityContract
|
||||||
import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER
|
import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER
|
||||||
import net.corda.finance.contracts.asset.DUMMY_OBLIGATION_ISSUER
|
import net.corda.finance.contracts.asset.DUMMY_OBLIGATION_ISSUER
|
||||||
import net.corda.testing.CHARLIE
|
import net.corda.testing.*
|
||||||
import net.corda.testing.DUMMY_NOTARY
|
|
||||||
import net.corda.testing.DUMMY_NOTARY_KEY
|
|
||||||
import net.corda.testing.dummyCommand
|
|
||||||
import net.corda.testing.chooseIdentity
|
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.Instant
|
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.evolveLinearStates(linearStates: List<StateAndRef<LinearState>>, notary: Party) = consumeAndProduce(linearStates, notary)
|
||||||
fun ServiceHub.evolveLinearState(linearState: StateAndRef<LinearState>, notary: Party) : StateAndRef<LinearState> = consumeAndProduce(linearState, 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
|
@JvmOverloads
|
||||||
fun ServiceHub.consumeCash(amount: Amount<Currency>, to: Party = CHARLIE, notary: Party): Vault.Update<ContractState> {
|
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 update = vaultService.rawUpdates.toFuture()
|
||||||
val services = this
|
val services = this
|
||||||
|
|
||||||
// A tx that spends our money.
|
// A tx that spends our money.
|
||||||
val builder = TransactionBuilder(notary).apply {
|
val builder = TransactionBuilder(notary).apply {
|
||||||
Cash.generateSpend(services, this, amount, to)
|
Cash.generateSpend(services, this, amount, ourIdentity, to)
|
||||||
}
|
}
|
||||||
val spendTx = signInitialTransaction(builder, notary.owningKey)
|
val spendTx = signInitialTransaction(builder, notary.owningKey)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user