From 332915f08bb201596e20d4aecde04cd6c7001dff Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Fri, 17 Nov 2017 14:15:49 +0000 Subject: [PATCH] Bug fix for cash selection on H2 where the accumulated pennies amount is larger than max int --- .../cash/selection/CashSelectionH2Impl.kt | 4 +--- .../selection/CashSelectionH2ImplTest.kt} | 22 +++++++++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) rename finance/src/test/kotlin/net/corda/finance/contracts/asset/{CashSelectionH2Test.kt => cash/selection/CashSelectionH2ImplTest.kt} (59%) diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt index d8be0af698..08fa686c07 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt @@ -13,7 +13,6 @@ import java.sql.ResultSet import java.util.* class CashSelectionH2Impl : AbstractCashSelection() { - companion object { const val JDBC_DRIVER_NAME = "H2 JDBC Driver" val log = loggerFor() @@ -25,7 +24,6 @@ class CashSelectionH2Impl : AbstractCashSelection() { 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: // 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 @@ -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) override fun executeQuery(connection: Connection, amount: Amount, lockId: UUID, notary: Party?, onlyFromIssuerParties: Set, withIssuerRefs: Set) : ResultSet { - connection.createStatement().execute("CALL SET(@t, 0);") + connection.createStatement().execute("CALL SET(@t, CAST(0 AS BIGINT));") val selectJoin = """ SELECT vs.transaction_id, vs.output_index, ccs.pennies, SET(@t, ifnull(@t,0)+ccs.pennies) total_pennies, vs.lock_id diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2ImplTest.kt similarity index 59% rename from finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt rename to finance/src/test/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2ImplTest.kt index 4f4a39abec..c044387f00 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2ImplTest.kt @@ -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.finance.DOLLARS +import net.corda.finance.POUNDS import net.corda.finance.flows.CashException +import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNodeParameters @@ -10,8 +14,9 @@ import net.corda.testing.startFlow import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.After import org.junit.Test +import java.util.Collections.nCopies -class CashSelectionH2Test { +class CashSelectionH2ImplTest { private val mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance")) @After @@ -19,6 +24,19 @@ class CashSelectionH2Test { 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 fun `check does not hold connection over retries`() { val bankA = mockNet.createNode(MockNodeParameters(configOverrides = {