mirror of
https://github.com/corda/corda.git
synced 2025-04-29 23:40:15 +00:00
Fixed subtle bug in Cash Spending when processing for same Issuer with multiple refs.
This commit is contained in:
parent
f415c497d9
commit
a38b363e9d
@ -486,7 +486,7 @@ class CashTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun makeSpend(amount: Amount<Currency>, dest: PublicKey): WireTransaction {
|
fun makeSpend(amount: Amount<Currency>, dest: PublicKey): WireTransaction {
|
||||||
var tx = TransactionType.General.Builder(DUMMY_NOTARY)
|
val tx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||||
databaseTransaction(database) {
|
databaseTransaction(database) {
|
||||||
vault.generateSpend(tx, amount, dest)
|
vault.generateSpend(tx, amount, dest)
|
||||||
}
|
}
|
||||||
@ -563,7 +563,7 @@ class CashTests {
|
|||||||
databaseTransaction(database) {
|
databaseTransaction(database) {
|
||||||
|
|
||||||
val tx = TransactionType.General.Builder(DUMMY_NOTARY)
|
val tx = TransactionType.General.Builder(DUMMY_NOTARY)
|
||||||
vault.generateSpend(tx, 80.DOLLARS, ALICE_PUBKEY, setOf(MINI_CORP.ref(1)))
|
vault.generateSpend(tx, 80.DOLLARS, ALICE_PUBKEY, setOf(MINI_CORP))
|
||||||
|
|
||||||
assertEquals(vaultService.states.elementAt(2).ref, tx.inputStates()[0])
|
assertEquals(vaultService.states.elementAt(2).ref, tx.inputStates()[0])
|
||||||
}
|
}
|
||||||
@ -612,8 +612,8 @@ class CashTests {
|
|||||||
assertEquals(vaultState0.ref, wtx.inputs[0])
|
assertEquals(vaultState0.ref, wtx.inputs[0])
|
||||||
assertEquals(vaultState1.ref, wtx.inputs[1])
|
assertEquals(vaultState1.ref, wtx.inputs[1])
|
||||||
assertEquals(vaultState2.ref, wtx.inputs[2])
|
assertEquals(vaultState2.ref, wtx.inputs[2])
|
||||||
assertEquals(vaultState0.state.data.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data)
|
assertEquals(vaultState0.state.data.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[1].data)
|
||||||
assertEquals(vaultState2.state.data.copy(owner = THEIR_PUBKEY_1), wtx.outputs[1].data)
|
assertEquals(vaultState2.state.data.copy(owner = THEIR_PUBKEY_1), wtx.outputs[0].data)
|
||||||
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
|
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,14 +166,14 @@ interface VaultService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* [InsufficientBalanceException] is thrown when a Cash Spending transaction fails because
|
* [InsufficientBalanceException] is thrown when a Cash Spending transaction fails because
|
||||||
* there is insufficient quantity for a given currency (and optionally set of Issuers).
|
* there is insufficient quantity for a given currency (and optionally set of Issuer Parties).
|
||||||
* Note: an [Amount] of [Currency] is only fungible for a given Issuer within a [FungibleAsset]
|
* Note: an [Amount] of [Currency] is only fungible for a given Issuer Party within a [FungibleAsset]
|
||||||
**/
|
**/
|
||||||
@Throws(InsufficientBalanceException::class)
|
@Throws(InsufficientBalanceException::class)
|
||||||
fun generateSpend(tx: TransactionBuilder,
|
fun generateSpend(tx: TransactionBuilder,
|
||||||
amount: Amount<Currency>,
|
amount: Amount<Currency>,
|
||||||
to: PublicKey,
|
to: PublicKey,
|
||||||
onlyFromIssuers: Set<PartyAndReference>? = null): Pair<TransactionBuilder, List<PublicKey>>
|
onlyFromParties: Set<Party>? = null): Pair<TransactionBuilder, List<PublicKey>>
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T : LinearState> VaultService.linearHeadsOfType() = linearHeadsOfType_(T::class.java)
|
inline fun <reified T : LinearState> VaultService.linearHeadsOfType() = linearHeadsOfType_(T::class.java)
|
||||||
|
@ -79,8 +79,9 @@ class ServerRPCOps(
|
|||||||
val builder: TransactionBuilder = TransactionType.General.Builder(null)
|
val builder: TransactionBuilder = TransactionType.General.Builder(null)
|
||||||
// TODO: Have some way of restricting this to states the caller controls
|
// TODO: Have some way of restricting this to states the caller controls
|
||||||
try {
|
try {
|
||||||
val (spendTX, keysForSigning) = services.vaultService.generateSpend(builder, req.amount.withoutIssuer(), req.recipient.owningKey,
|
val (spendTX, keysForSigning) = services.vaultService.generateSpend(builder,
|
||||||
setOf(req.amount.token.issuer))
|
req.amount.withoutIssuer(), req.recipient.owningKey, setOf(req.amount.token.issuer.party))
|
||||||
|
|
||||||
keysForSigning.forEach {
|
keysForSigning.forEach {
|
||||||
val key = services.keyManagementService.keys[it] ?: throw IllegalStateException("Could not find signing key for ${it.toStringShort()}")
|
val key = services.keyManagementService.keys[it] ?: throw IllegalStateException("Could not find signing key for ${it.toStringShort()}")
|
||||||
builder.signWith(KeyPair(it, key))
|
builder.signWith(KeyPair(it, key))
|
||||||
|
@ -120,7 +120,7 @@ class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsT
|
|||||||
override fun generateSpend(tx: TransactionBuilder,
|
override fun generateSpend(tx: TransactionBuilder,
|
||||||
amount: Amount<Currency>,
|
amount: Amount<Currency>,
|
||||||
to: PublicKey,
|
to: PublicKey,
|
||||||
onlyFromIssuers: Set<PartyAndReference>?): Pair<TransactionBuilder, List<PublicKey>> {
|
onlyFromParties: Set<Party>?): Pair<TransactionBuilder, List<PublicKey>> {
|
||||||
// Discussion
|
// Discussion
|
||||||
//
|
//
|
||||||
// This code is analogous to the Wallet.send() set of methods in bitcoinj, and has the same general outline.
|
// This code is analogous to the Wallet.send() set of methods in bitcoinj, and has the same general outline.
|
||||||
@ -146,8 +146,8 @@ class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsT
|
|||||||
val currency = amount.token
|
val currency = amount.token
|
||||||
var acceptableCoins = run {
|
var acceptableCoins = run {
|
||||||
val ofCurrency = assetsStates.filter { it.state.data.amount.token.product == currency }
|
val ofCurrency = assetsStates.filter { it.state.data.amount.token.product == currency }
|
||||||
if (onlyFromIssuers != null)
|
if (onlyFromParties != null)
|
||||||
ofCurrency.filter { it.state.data.amount.token.issuer in onlyFromIssuers }
|
ofCurrency.filter { it.state.data.amount.token.issuer.party in onlyFromParties }
|
||||||
else
|
else
|
||||||
ofCurrency
|
ofCurrency
|
||||||
}
|
}
|
||||||
@ -170,17 +170,22 @@ class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsT
|
|||||||
val coins = it.value
|
val coins = it.value
|
||||||
val totalAmount = coins.map { it.state.data.amount }.sumOrThrow()
|
val totalAmount = coins.map { it.state.data.amount }.sumOrThrow()
|
||||||
deriveState(coins.first().state, totalAmount, to)
|
deriveState(coins.first().state, totalAmount, to)
|
||||||
}
|
}.sortedBy { it.data.amount.quantity }
|
||||||
|
|
||||||
val outputs = if (change != null) {
|
val outputs = if (change != null) {
|
||||||
// Just copy a key across as the change key. In real life of course, this works but leaks private data.
|
// Just copy a key across as the change key. In real life of course, this works but leaks private data.
|
||||||
// In bitcoinj we derive a fresh key here and then shuffle the outputs to ensure it's hard to follow
|
// In bitcoinj we derive a fresh key here and then shuffle the outputs to ensure it's hard to follow
|
||||||
// value flows through the transaction graph.
|
// value flows through the transaction graph.
|
||||||
val changeKey = gathered.first().state.data.owner
|
val existingOwner = gathered.first().state.data.owner
|
||||||
// Add a change output and adjust the last output downwards.
|
// Add a change output and adjust the last output downwards.
|
||||||
states.subList(0, states.lastIndex) +
|
states.subList(0, states.lastIndex) +
|
||||||
states.last().let { deriveState(it, it.data.amount - change, it.data.owner) } +
|
states.last().let {
|
||||||
deriveState(gathered.last().state, change, changeKey)
|
val spent = it.data.amount.withoutIssuer() - change.withoutIssuer()
|
||||||
|
deriveState(it, Amount(spent.quantity, it.data.amount.token), it.data.owner)
|
||||||
|
} +
|
||||||
|
states.last().let {
|
||||||
|
deriveState(it, Amount(change.quantity, it.data.amount.token), existingOwner)
|
||||||
|
}
|
||||||
} else states
|
} else states
|
||||||
|
|
||||||
for (state in gathered) tx.addInputState(state)
|
for (state in gathered) tx.addInputState(state)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user