CORDA-1506: Cash selection logic fails when selection with a change from more that 2 different issuer groups (#3187)

* Expose a flaw in cash selection logic

* Minimal test exposing the bug.

* Remaining unspent cash overwrites the cash of the first issuer, not the current issuer - the problem emerged when at least three different groups were selected, for 2 different issuers the last one in selection process was always the first remaining one.

* Addressing PR comments + more precise test name

* Addressing PR comments.
This commit is contained in:
Viktor Kolomeyko 2018-05-18 17:16:41 +01:00 committed by GitHub
parent 013eb33d7c
commit fa090eb865
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 17 additions and 1 deletions

View File

@ -152,7 +152,7 @@ abstract class OnLedgerAsset<T : Any, out C : CommandData, S : FungibleAsset<T>>
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))
remainingFromEachIssuer[remainingFromEachIssuer.lastIndex] = Pair(token, Amount(delta, token))
remainingToPay = 0
}
delta == 0L -> {

View File

@ -14,6 +14,7 @@ import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.After
import org.junit.Test
import java.util.Collections.nCopies
import kotlin.test.assertNotNull
class CashSelectionH2ImplTest {
private val mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance"))
@ -53,4 +54,19 @@ class CashSelectionH2ImplTest {
assertThatThrownBy { flow2.getOrThrow() }.isInstanceOf(CashException::class.java)
assertThatThrownBy { flow3.getOrThrow() }.isInstanceOf(CashException::class.java)
}
@Test
fun `select pennies amount from cash states with more than two different issuers and expect change`() {
val node = mockNet.createNode()
val notary = mockNet.defaultNotaryIdentity
// Issue some cash
node.startFlow(CashIssueFlow(1.POUNDS, OpaqueBytes.of(1), notary)).getOrThrow()
node.startFlow(CashIssueFlow(1.POUNDS, OpaqueBytes.of(2), notary)).getOrThrow()
node.startFlow(CashIssueFlow(1000.POUNDS, OpaqueBytes.of(3), notary)).getOrThrow()
// Make a payment
val paymentResult = node.startFlow(CashPaymentFlow(999.POUNDS, node.info.legalIdentities[0], false)).getOrThrow()
assertNotNull(paymentResult.recipient)
}
}