mirror of
https://github.com/corda/corda.git
synced 2025-06-22 00:57:21 +00:00
Bug fix for cash selection on H2 where the accumulated pennies amount is larger than max int
This commit is contained in:
@ -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
|
||||||
|
@ -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 = {
|
Reference in New Issue
Block a user