Bug fix for cash selection on H2 where the accumulated pennies amount is larger than max int

This commit is contained in:
Shams Asari
2017-11-17 14:15:49 +00:00
parent 8e7165db41
commit 332915f08b
2 changed files with 21 additions and 5 deletions

View File

@ -13,7 +13,6 @@ import java.sql.ResultSet
import java.util.* import java.util.*
class CashSelectionH2Impl : AbstractCashSelection() { class CashSelectionH2Impl : AbstractCashSelection() {
companion object { companion object {
const val JDBC_DRIVER_NAME = "H2 JDBC Driver" const val JDBC_DRIVER_NAME = "H2 JDBC Driver"
val log = loggerFor<CashSelectionH2Impl>() val log = loggerFor<CashSelectionH2Impl>()
@ -25,7 +24,6 @@ class CashSelectionH2Impl : AbstractCashSelection() {
override fun toString() = "${this::class.java} for $JDBC_DRIVER_NAME" override fun toString() = "${this::class.java} for $JDBC_DRIVER_NAME"
// We are using an H2 specific means of selecting a minimum set of rows that match a request amount of coins: // We are using an H2 specific means of selecting a minimum set of rows that match a request amount of coins:
// 1) There is no standard SQL mechanism of calculating a cumulative total on a field and restricting row selection on the // 1) There is no standard SQL mechanism of calculating a cumulative total on a field and restricting row selection on the
// running total of such an accumulator // running total of such an accumulator
@ -34,7 +32,7 @@ class CashSelectionH2Impl : AbstractCashSelection() {
// 3) H2 does not support JOIN's in FOR UPDATE (hence we are forced to execute 2 queries) // 3) H2 does not support JOIN's in FOR UPDATE (hence we are forced to execute 2 queries)
override fun executeQuery(connection: Connection, amount: Amount<Currency>, lockId: UUID, notary: Party?, override fun executeQuery(connection: Connection, amount: Amount<Currency>, lockId: UUID, notary: Party?,
onlyFromIssuerParties: Set<AbstractParty>, withIssuerRefs: Set<OpaqueBytes>) : ResultSet { onlyFromIssuerParties: Set<AbstractParty>, withIssuerRefs: Set<OpaqueBytes>) : ResultSet {
connection.createStatement().execute("CALL SET(@t, 0);") connection.createStatement().execute("CALL SET(@t, CAST(0 AS BIGINT));")
val selectJoin = """ val selectJoin = """
SELECT vs.transaction_id, vs.output_index, ccs.pennies, SET(@t, ifnull(@t,0)+ccs.pennies) total_pennies, vs.lock_id SELECT vs.transaction_id, vs.output_index, ccs.pennies, SET(@t, ifnull(@t,0)+ccs.pennies) total_pennies, vs.lock_id

View File

@ -1,8 +1,12 @@
package net.corda.finance.contracts.asset package net.corda.finance.contracts.asset.cash.selection
import net.corda.core.internal.concurrent.transpose
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS import net.corda.finance.DOLLARS
import net.corda.finance.POUNDS
import net.corda.finance.flows.CashException import net.corda.finance.flows.CashException
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow import net.corda.finance.flows.CashPaymentFlow
import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNodeParameters import net.corda.testing.node.MockNodeParameters
@ -10,8 +14,9 @@ import net.corda.testing.startFlow
import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.After import org.junit.After
import org.junit.Test import org.junit.Test
import java.util.Collections.nCopies
class CashSelectionH2Test { class CashSelectionH2ImplTest {
private val mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance")) private val mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance"))
@After @After
@ -19,6 +24,19 @@ class CashSelectionH2Test {
mockNet.stopNodes() mockNet.stopNodes()
} }
@Test
fun `selecting pennies amount larger than max int, which is split across multiple cash states`() {
val node = mockNet.createNode()
// The amount has to split across at least two states, probably to trigger the H2 accumulator variable during the
// spend operation below.
// Issuing Integer.MAX_VALUE will not cause an exception since PersistentCashState.pennies is a long
nCopies(2, Integer.MAX_VALUE).map { issueAmount ->
node.services.startFlow(CashIssueFlow(issueAmount.POUNDS, OpaqueBytes.of(1), mockNet.defaultNotaryIdentity)).resultFuture
}.transpose().getOrThrow()
// The spend must be more than the size of a single cash state to force the accumulator onto the second state.
node.services.startFlow(CashPaymentFlow((Integer.MAX_VALUE + 1L).POUNDS, node.info.legalIdentities[0])).resultFuture.getOrThrow()
}
@Test @Test
fun `check does not hold connection over retries`() { fun `check does not hold connection over retries`() {
val bankA = mockNet.createNode(MockNodeParameters(configOverrides = { val bankA = mockNet.createNode(MockNodeParameters(configOverrides = {