From 30e8fd83c256826fc995edae499bf8bb6b60b7f2 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Fri, 26 Aug 2022 01:18:01 +0200 Subject: [PATCH] wallet-core: fix revocation, re-introduce reserves object store --- .../integrationtests/test-libeufin-basic.ts | 2 +- .../test-libeufin-nexus-balance.ts | 15 +- .../src/integrationtests/testrunner.ts | 106 ++++++------- packages/taler-wallet-core/src/db.ts | 29 +++- .../src/internal-wallet-state.ts | 2 +- .../src/operations/exchanges.ts | 1 + .../src/operations/pending.ts | 2 +- .../src/operations/recoup.ts | 148 +++++++++++++----- .../src/operations/withdraw.ts | 23 ++- .../taler-wallet-core/src/pending-types.ts | 2 +- 10 files changed, 218 insertions(+), 112 deletions(-) diff --git a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts index ca7dc33d8..83231b358 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts @@ -243,7 +243,7 @@ export async function runLibeufinBasicTest(t: GlobalTestState) { WalletApiOperation.AcceptManualWithdrawal, { exchangeBaseUrl: exchange.baseUrl, - amount: "EUR:10", + amount: "EUR:15", }, ); diff --git a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-nexus-balance.ts b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-nexus-balance.ts index 23d76081f..ff7a50ae6 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-nexus-balance.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-nexus-balance.ts @@ -17,12 +17,11 @@ /** * Imports. */ -import { GlobalTestState, delayMs } from "../harness/harness.js"; +import { GlobalTestState } from "../harness/harness.js"; import { SandboxUserBundle, NexusUserBundle, launchLibeufinServices, - LibeufinSandboxApi, LibeufinNexusApi, } from "../harness/libeufin"; @@ -73,7 +72,7 @@ export async function runLibeufinNexusBalanceTest(t: GlobalTestState) { user02sandbox.ebicsBankAccount.label, // debit user01sandbox.ebicsBankAccount.label, // credit "EUR:10", - "first payment", + "second payment", ); await LibeufinNexusApi.fetchTransactions( @@ -82,13 +81,13 @@ export async function runLibeufinNexusBalanceTest(t: GlobalTestState) { "all", // range "report", // level ); - + // Check that user 01 has 20, via Nexus. let accountInfo = await LibeufinNexusApi.getBankAccount( libeufinServices.libeufinNexus, - user01nexus.localAccountName + user01nexus.localAccountName, ); - t.assertTrue(accountInfo.data.lastSeenBalance == "EUR:20"); + t.assertAmountEquals(accountInfo.data.lastSeenBalance, "EUR:20"); // user 01 gives 30 await libeufinServices.libeufinSandbox.makeTransaction( @@ -107,8 +106,8 @@ export async function runLibeufinNexusBalanceTest(t: GlobalTestState) { let accountInfoDebit = await LibeufinNexusApi.getBankAccount( libeufinServices.libeufinNexus, - user01nexus.localAccountName + user01nexus.localAccountName, ); - t.assertTrue(accountInfoDebit.data.lastSeenBalance == "-EUR:10"); + t.assertDeepEqual(accountInfoDebit.data.lastSeenBalance, "-EUR:10"); } runLibeufinNexusBalanceTest.suites = ["libeufin"]; diff --git a/packages/taler-wallet-cli/src/integrationtests/testrunner.ts b/packages/taler-wallet-cli/src/integrationtests/testrunner.ts index 88e67a8bb..74aa66005 100644 --- a/packages/taler-wallet-cli/src/integrationtests/testrunner.ts +++ b/packages/taler-wallet-cli/src/integrationtests/testrunner.ts @@ -26,71 +26,71 @@ import { TestRunResult, } from "../harness/harness.js"; import { runAgeRestrictionsTest } from "./test-age-restrictions.js"; -import { runBankApiTest } from "./test-bank-api"; -import { runClaimLoopTest } from "./test-claim-loop"; +import { runBankApiTest } from "./test-bank-api.js"; +import { runClaimLoopTest } from "./test-claim-loop.js"; import { runClauseSchnorrTest } from "./test-clause-schnorr.js"; import { runDenomUnofferedTest } from "./test-denom-unoffered.js"; -import { runDepositTest } from "./test-deposit"; -import { runExchangeManagementTest } from "./test-exchange-management"; +import { runDepositTest } from "./test-deposit.js"; +import { runExchangeManagementTest } from "./test-exchange-management.js"; import { runExchangeTimetravelTest } from "./test-exchange-timetravel.js"; -import { runFeeRegressionTest } from "./test-fee-regression"; +import { runFeeRegressionTest } from "./test-fee-regression.js"; import { runForcedSelectionTest } from "./test-forced-selection.js"; -import { runLibeufinApiBankaccountTest } from "./test-libeufin-api-bankaccount"; -import { runLibeufinApiBankconnectionTest } from "./test-libeufin-api-bankconnection"; -import { runLibeufinApiFacadeTest } from "./test-libeufin-api-facade"; -import { runLibeufinApiFacadeBadRequestTest } from "./test-libeufin-api-facade-bad-request"; -import { runLibeufinApiPermissionsTest } from "./test-libeufin-api-permissions"; -import { runLibeufinApiSandboxCamtTest } from "./test-libeufin-api-sandbox-camt"; -import { runLibeufinApiSandboxTransactionsTest } from "./test-libeufin-api-sandbox-transactions"; -import { runLibeufinApiSchedulingTest } from "./test-libeufin-api-scheduling"; -import { runLibeufinApiUsersTest } from "./test-libeufin-api-users"; -import { runLibeufinBadGatewayTest } from "./test-libeufin-bad-gateway"; -import { runLibeufinBasicTest } from "./test-libeufin-basic"; -import { runLibeufinC5xTest } from "./test-libeufin-c5x"; -import { runLibeufinAnastasisFacadeTest } from "./test-libeufin-facade-anastasis"; -import { runLibeufinKeyrotationTest } from "./test-libeufin-keyrotation"; -import { runLibeufinNexusBalanceTest } from "./test-libeufin-nexus-balance"; -import { runLibeufinRefundTest } from "./test-libeufin-refund"; -import { runLibeufinRefundMultipleUsersTest } from "./test-libeufin-refund-multiple-users"; -import { runLibeufinSandboxWireTransferCliTest } from "./test-libeufin-sandbox-wire-transfer-cli"; -import { runLibeufinTutorialTest } from "./test-libeufin-tutorial"; -import { runMerchantExchangeConfusionTest } from "./test-merchant-exchange-confusion"; -import { runMerchantInstancesTest } from "./test-merchant-instances"; +import { runLibeufinApiBankaccountTest } from "./test-libeufin-api-bankaccount.js"; +import { runLibeufinApiBankconnectionTest } from "./test-libeufin-api-bankconnection.js"; +import { runLibeufinApiFacadeTest } from "./test-libeufin-api-facade.js"; +import { runLibeufinApiFacadeBadRequestTest } from "./test-libeufin-api-facade-bad-request.js"; +import { runLibeufinApiPermissionsTest } from "./test-libeufin-api-permissions.js"; +import { runLibeufinApiSandboxCamtTest } from "./test-libeufin-api-sandbox-camt.js"; +import { runLibeufinApiSandboxTransactionsTest } from "./test-libeufin-api-sandbox-transactions.js"; +import { runLibeufinApiSchedulingTest } from "./test-libeufin-api-scheduling.js"; +import { runLibeufinApiUsersTest } from "./test-libeufin-api-users.js"; +import { runLibeufinBadGatewayTest } from "./test-libeufin-bad-gateway.js"; +import { runLibeufinBasicTest } from "./test-libeufin-basic.js"; +import { runLibeufinC5xTest } from "./test-libeufin-c5x.js"; +import { runLibeufinAnastasisFacadeTest } from "./test-libeufin-facade-anastasis.js"; +import { runLibeufinKeyrotationTest } from "./test-libeufin-keyrotation.js"; +import { runLibeufinNexusBalanceTest } from "./test-libeufin-nexus-balance.js"; +import { runLibeufinRefundTest } from "./test-libeufin-refund.js"; +import { runLibeufinRefundMultipleUsersTest } from "./test-libeufin-refund-multiple-users.js"; +import { runLibeufinSandboxWireTransferCliTest } from "./test-libeufin-sandbox-wire-transfer-cli.js"; +import { runLibeufinTutorialTest } from "./test-libeufin-tutorial.js"; +import { runMerchantExchangeConfusionTest } from "./test-merchant-exchange-confusion.js"; +import { runMerchantInstancesTest } from "./test-merchant-instances.js"; import { runMerchantInstancesDeleteTest } from "./test-merchant-instances-delete"; -import { runMerchantInstancesUrlsTest } from "./test-merchant-instances-urls"; -import { runMerchantLongpollingTest } from "./test-merchant-longpolling"; -import { runMerchantRefundApiTest } from "./test-merchant-refund-api"; +import { runMerchantInstancesUrlsTest } from "./test-merchant-instances-urls.js"; +import { runMerchantLongpollingTest } from "./test-merchant-longpolling.js"; +import { runMerchantRefundApiTest } from "./test-merchant-refund-api.js"; import { runMerchantSpecPublicOrdersTest } from "./test-merchant-spec-public-orders.js"; -import { runPayAbortTest } from "./test-pay-abort"; -import { runPayPaidTest } from "./test-pay-paid"; -import { runPaymentTest } from "./test-payment"; -import { runPaymentClaimTest } from "./test-payment-claim"; -import { runPaymentFaultTest } from "./test-payment-fault"; +import { runPayAbortTest } from "./test-pay-abort.js"; +import { runPayPaidTest } from "./test-pay-paid.js"; +import { runPaymentTest } from "./test-payment.js"; +import { runPaymentClaimTest } from "./test-payment-claim.js"; +import { runPaymentFaultTest } from "./test-payment-fault.js"; import { runPaymentForgettableTest } from "./test-payment-forgettable.js"; -import { runPaymentIdempotencyTest } from "./test-payment-idempotency"; -import { runPaymentMultipleTest } from "./test-payment-multiple"; -import { runPaymentDemoTest } from "./test-payment-on-demo"; -import { runPaymentTransientTest } from "./test-payment-transient"; +import { runPaymentIdempotencyTest } from "./test-payment-idempotency.js"; +import { runPaymentMultipleTest } from "./test-payment-multiple.js"; +import { runPaymentDemoTest } from "./test-payment-on-demo.js"; +import { runPaymentTransientTest } from "./test-payment-transient.js"; import { runPaymentZeroTest } from "./test-payment-zero.js"; -import { runPaywallFlowTest } from "./test-paywall-flow"; +import { runPaywallFlowTest } from "./test-paywall-flow.js"; import { runPeerToPeerPullTest } from "./test-peer-to-peer-pull.js"; import { runPeerToPeerPushTest } from "./test-peer-to-peer-push.js"; -import { runRefundTest } from "./test-refund"; -import { runRefundAutoTest } from "./test-refund-auto"; -import { runRefundGoneTest } from "./test-refund-gone"; -import { runRefundIncrementalTest } from "./test-refund-incremental"; -import { runRevocationTest } from "./test-revocation"; -import { runTimetravelAutorefreshTest } from "./test-timetravel-autorefresh"; -import { runTimetravelWithdrawTest } from "./test-timetravel-withdraw"; -import { runTippingTest } from "./test-tipping"; -import { runWalletBackupBasicTest } from "./test-wallet-backup-basic"; -import { runWalletBackupDoublespendTest } from "./test-wallet-backup-doublespend"; +import { runRefundTest } from "./test-refund.js"; +import { runRefundAutoTest } from "./test-refund-auto.js"; +import { runRefundGoneTest } from "./test-refund-gone.js"; +import { runRefundIncrementalTest } from "./test-refund-incremental.js"; +import { runRevocationTest } from "./test-revocation.js"; +import { runTimetravelAutorefreshTest } from "./test-timetravel-autorefresh.js"; +import { runTimetravelWithdrawTest } from "./test-timetravel-withdraw.js"; +import { runTippingTest } from "./test-tipping.js"; +import { runWalletBackupBasicTest } from "./test-wallet-backup-basic.js"; +import { runWalletBackupDoublespendTest } from "./test-wallet-backup-doublespend.js"; import { runWalletDblessTest } from "./test-wallet-dbless.js"; -import { runWallettestingTest } from "./test-wallettesting"; -import { runWithdrawalAbortBankTest } from "./test-withdrawal-abort-bank"; -import { runWithdrawalBankIntegratedTest } from "./test-withdrawal-bank-integrated"; +import { runWallettestingTest } from "./test-wallettesting.js"; +import { runWithdrawalAbortBankTest } from "./test-withdrawal-abort-bank.js"; +import { runWithdrawalBankIntegratedTest } from "./test-withdrawal-bank-integrated.js"; import { runWithdrawalFakebankTest } from "./test-withdrawal-fakebank.js"; -import { runTestWithdrawalManualTest } from "./test-withdrawal-manual"; +import { runTestWithdrawalManualTest } from "./test-withdrawal-manual.js"; /** * Test runner. diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 7ba0fadc6..3f97be045 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -1224,6 +1224,7 @@ export const enum WithdrawalRecordType { BankIntegrated = "bank-integrated", PeerPullCredit = "peer-pull-credit", PeerPushCredit = "peer-push-credit", + Recoup = "recoup", } export interface WgInfoBankIntegrated { @@ -1253,11 +1254,16 @@ export interface WgInfoBankPeerPush { withdrawalType: WithdrawalRecordType.PeerPushCredit; } +export interface WgInfoBankRecoup { + withdrawalType: WithdrawalRecordType.Recoup; +} + export type WgInfo = | WgInfoBankIntegrated | WgInfoBankManual | WgInfoBankPeerPull - | WgInfoBankPeerPush; + | WgInfoBankPeerPush + | WgInfoBankRecoup; /** * Group of withdrawal operations that need to be executed. @@ -1287,6 +1293,8 @@ export interface WithdrawalGroupRecord { /** * The reserve private key. + * + * FIXME: Already in the reserves object store, redundant! */ reservePriv: string; @@ -1355,9 +1363,9 @@ export interface WithdrawalGroupRecord { denomSelUid: string; /** - * Retry info, always present even on completed operations so that indexing works. + * Retry info. */ - retryInfo: RetryInfo; + retryInfo?: RetryInfo; lastError: TalerErrorDetail | undefined; } @@ -1386,6 +1394,8 @@ export interface RecoupGroupRecord { */ recoupGroupId: string; + exchangeBaseUrl: string; + timestampStarted: TalerProtocolTimestamp; timestampFinished: TalerProtocolTimestamp | undefined; @@ -1724,6 +1734,13 @@ export interface PeerPullPaymentIncomingRecord { contractPriv: string; } +// FIXME: give this some smaller "row ID" to +// reference in other records? +export interface ReserveRecord { + reservePub: string; + reservePriv: string; +} + export const WalletStoresV1 = { coins: describeStore( describeContents("coins", { @@ -1735,6 +1752,12 @@ export const WalletStoresV1 = { byCoinEvHash: describeIndex("byCoinEvHash", "coinEvHash"), }, ), + reserves: describeStore( + describeContents("reserves", { + keyPath: "reservePub", + }), + {}, + ), config: describeStore( describeContents("config", { keyPath: "key" }), {}, diff --git a/packages/taler-wallet-core/src/internal-wallet-state.ts b/packages/taler-wallet-core/src/internal-wallet-state.ts index 0650ed040..e82bc139b 100644 --- a/packages/taler-wallet-core/src/internal-wallet-state.ts +++ b/packages/taler-wallet-core/src/internal-wallet-state.ts @@ -73,7 +73,6 @@ export interface MerchantOperations { ): Promise; } - /** * Interface for exchange-related operations. */ @@ -113,6 +112,7 @@ export interface RecoupOperations { refreshGroups: typeof WalletStoresV1.refreshGroups; coins: typeof WalletStoresV1.coins; }>, + exchangeBaseUrl: string, coinPubs: string[], ): Promise; processRecoupGroup( diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index 94ea2cb9c..b75bdfd74 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -743,6 +743,7 @@ async function updateExchangeFromUrlImpl( recoupGroupId = await ws.recoupOps.createRecoupGroup( ws, tx, + exchange.baseUrl, newlyRevokedCoinPubs, ); } diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts index ae93711f9..38146f72e 100644 --- a/packages/taler-wallet-core/src/operations/pending.ts +++ b/packages/taler-wallet-core/src/operations/pending.ts @@ -126,7 +126,7 @@ async function gatherWithdrawalPending( resp.pendingOperations.push({ type: PendingTaskType.Withdraw, givesLifeness: true, - timestampDue: wsr.retryInfo.nextRetry, + timestampDue: wsr.retryInfo?.nextRetry ?? AbsoluteTime.now(), withdrawalGroupId: wsr.withdrawalGroupId, lastError: wsr.lastError, retryInfo: wsr.retryInfo, diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts index 7c0f79daf..283707947 100644 --- a/packages/taler-wallet-core/src/operations/recoup.ts +++ b/packages/taler-wallet-core/src/operations/recoup.ts @@ -36,16 +36,17 @@ import { TalerErrorDetail, TalerProtocolTimestamp, URL, + codecForReserveStatus, } from "@gnu-taler/taler-util"; import { CoinRecord, CoinSourceType, CoinStatus, - OperationStatus, RecoupGroupRecord, RefreshCoinSource, ReserveRecordStatus, WalletStoresV1, + WithdrawalRecordType, WithdrawCoinSource, } from "../db.js"; import { InternalWalletState } from "../internal-wallet-state.js"; @@ -109,6 +110,10 @@ async function reportRecoupError( ws.notify({ type: NotificationType.RecoupOperationError, error: err }); } +/** + * Store a recoup group record in the database after marking + * a coin in the group as finished. + */ async function putGroupAsFinished( ws: InternalWalletState, tx: GetReadWriteAccess<{ @@ -127,29 +132,6 @@ async function putGroupAsFinished( return; } recoupGroup.recoupFinishedPerCoin[coinIdx] = true; - let allFinished = true; - for (const b of recoupGroup.recoupFinishedPerCoin) { - if (!b) { - allFinished = false; - } - } - if (allFinished) { - logger.info("all recoups of recoup group are finished"); - recoupGroup.timestampFinished = TalerProtocolTimestamp.now(); - recoupGroup.retryInfo = RetryInfo.reset(); - recoupGroup.lastError = undefined; - if (recoupGroup.scheduleRefreshCoins.length > 0) { - const refreshGroupId = await createRefreshGroup( - ws, - tx, - recoupGroup.scheduleRefreshCoins.map((x) => ({ coinPub: x })), - RefreshReason.Recoup, - ); - processRefreshGroup(ws, refreshGroupId.refreshGroupId).catch((e) => { - logger.error(`error while refreshing after recoup ${e}`); - }); - } - } await tx.recoupGroups.put(recoupGroup); } @@ -258,8 +240,6 @@ async function recoupWithdrawCoin( const currency = updatedCoin.currentAmount.currency; updatedCoin.currentAmount = Amounts.getZero(currency); await tx.coins.put(updatedCoin); - // FIXME: Actually withdraw here! - // await internalCreateWithdrawalGroup(ws, {...}); await putGroupAsFinished(ws, tx, recoupGroup, coinIdx); }); @@ -392,7 +372,7 @@ async function processRecoupGroupImpl( ): Promise { const forceNow = options.forceNow ?? false; await setupRecoupRetry(ws, recoupGroupId, { reset: forceNow }); - const recoupGroup = await ws.db + let recoupGroup = await ws.db .mktx((x) => ({ recoupGroups: x.recoupGroups, })) @@ -416,23 +396,105 @@ async function processRecoupGroupImpl( }); await Promise.all(ps); - const reserveSet = new Set(); - for (let i = 0; i < recoupGroup.coinPubs.length; i++) { - const coinPub = recoupGroup.coinPubs[i]; - const coin = await ws.db - .mktx((x) => ({ - coins: x.coins, - })) - .runReadOnly(async (tx) => { - return tx.coins.get(coinPub); - }); - if (!coin) { - throw Error(`Coin ${coinPub} not found, can't request recoup`); - } - if (coin.coinSource.type === CoinSourceType.Withdraw) { - reserveSet.add(coin.coinSource.reservePub); + recoupGroup = await ws.db + .mktx((x) => ({ + recoupGroups: x.recoupGroups, + })) + .runReadOnly(async (tx) => { + return tx.recoupGroups.get(recoupGroupId); + }); + if (!recoupGroup) { + return; + } + + for (const b of recoupGroup.recoupFinishedPerCoin) { + if (!b) { + return; } } + + logger.info("all recoups of recoup group are finished"); + + const reserveSet = new Set(); + const reservePrivMap: Record = {}; + for (let i = 0; i < recoupGroup.coinPubs.length; i++) { + const coinPub = recoupGroup.coinPubs[i]; + await ws.db + .mktx((x) => ({ + coins: x.coins, + reserves: x.reserves, + })) + .runReadOnly(async (tx) => { + const coin = await tx.coins.get(coinPub); + if (!coin) { + throw Error(`Coin ${coinPub} not found, can't request recoup`); + } + if (coin.coinSource.type === CoinSourceType.Withdraw) { + const reserve = await tx.reserves.get(coin.coinSource.reservePub); + if (!reserve) { + return; + } + reserveSet.add(coin.coinSource.reservePub); + reservePrivMap[coin.coinSource.reservePub] = reserve.reservePriv; + } + }); + } + + for (const reservePub of reserveSet) { + const reserveUrl = new URL( + `reserves/${reservePub}`, + recoupGroup.exchangeBaseUrl, + ); + logger.info(`querying reserve status for recoup via ${reserveUrl}`); + + const resp = await ws.http.get(reserveUrl.href); + + const result = await readSuccessResponseJsonOrThrow( + resp, + codecForReserveStatus(), + ); + await internalCreateWithdrawalGroup(ws, { + amount: Amounts.parseOrThrow(result.balance), + exchangeBaseUrl: recoupGroup.exchangeBaseUrl, + reserveStatus: ReserveRecordStatus.QueryingStatus, + reserveKeyPair: { + pub: reservePub, + priv: reservePrivMap[reservePub], + }, + wgInfo: { + withdrawalType: WithdrawalRecordType.Recoup, + }, + }); + } + + await ws.db + .mktx((x) => ({ + recoupGroups: x.recoupGroups, + denominations: WalletStoresV1.denominations, + refreshGroups: WalletStoresV1.refreshGroups, + coins: WalletStoresV1.coins, + })) + .runReadWrite(async (tx) => { + const rg2 = await tx.recoupGroups.get(recoupGroupId); + if (!rg2) { + return; + } + rg2.timestampFinished = TalerProtocolTimestamp.now(); + rg2.retryInfo = RetryInfo.reset(); + rg2.lastError = undefined; + if (rg2.scheduleRefreshCoins.length > 0) { + const refreshGroupId = await createRefreshGroup( + ws, + tx, + rg2.scheduleRefreshCoins.map((x) => ({ coinPub: x })), + RefreshReason.Recoup, + ); + processRefreshGroup(ws, refreshGroupId.refreshGroupId).catch((e) => { + logger.error(`error while refreshing after recoup ${e}`); + }); + } + await tx.recoupGroups.put(rg2); + }); } export async function createRecoupGroup( @@ -443,12 +505,14 @@ export async function createRecoupGroup( refreshGroups: typeof WalletStoresV1.refreshGroups; coins: typeof WalletStoresV1.coins; }>, + exchangeBaseUrl: string, coinPubs: string[], ): Promise { const recoupGroupId = encodeCrock(getRandomBytes(32)); const recoupGroup: RecoupGroupRecord = { recoupGroupId, + exchangeBaseUrl: exchangeBaseUrl, coinPubs: coinPubs, lastError: undefined, timestampFinished: undefined, diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index a33f59162..84890a043 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -1135,6 +1135,22 @@ async function processWithdrawGroupImpl( withdrawalGroup.exchangeBaseUrl, ); + if (withdrawalGroup.denomsSel.selectedDenoms.length === 0) { + await ws.db + .mktx((x) => ({ withdrawalGroups: x.withdrawalGroups })) + .runReadWrite(async (tx) => { + const wg = await tx.withdrawalGroups.get(withdrawalGroupId); + if (!wg) { + return; + } + wg.operationStatus = OperationStatus.Finished; + delete wg.lastError; + delete wg.retryInfo; + await tx.withdrawalGroups.put(wg); + }); + return; + } + const numTotalCoins = withdrawalGroup.denomsSel.selectedDenoms .map((x) => x.count) .reduce((a, b) => a + b); @@ -1709,7 +1725,6 @@ export async function internalCreateWithdrawalGroup( args: { reserveStatus: ReserveRecordStatus; amount: AmountJson; - bankInfo?: ReserveBankInfo; exchangeBaseUrl: string; forcedDenomSel?: ForcedDenomSel; reserveKeyPair?: EddsaKeypair; @@ -1776,12 +1791,17 @@ export async function internalCreateWithdrawalGroup( await ws.db .mktx((x) => ({ withdrawalGroups: x.withdrawalGroups, + reserves: x.reserves, exchanges: x.exchanges, exchangeDetails: x.exchangeDetails, exchangeTrust: x.exchangeTrust, })) .runReadWrite(async (tx) => { await tx.withdrawalGroups.add(withdrawalGroup); + await tx.reserves.put({ + reservePub: withdrawalGroup.reservePub, + reservePriv: withdrawalGroup.reservePriv, + }); if (!isAudited && !isTrusted) { await tx.exchangeTrust.put({ @@ -1906,7 +1926,6 @@ export async function createManualWithdrawal( withdrawalType: WithdrawalRecordType.BankManual, }, exchangeBaseUrl: req.exchangeBaseUrl, - bankInfo: undefined, forcedDenomSel: req.forcedDenomSel, restrictAge: req.restrictAge, reserveStatus: ReserveRecordStatus.QueryingStatus, diff --git a/packages/taler-wallet-core/src/pending-types.ts b/packages/taler-wallet-core/src/pending-types.ts index e372a593d..39df9d0cb 100644 --- a/packages/taler-wallet-core/src/pending-types.ts +++ b/packages/taler-wallet-core/src/pending-types.ts @@ -182,7 +182,7 @@ export interface PendingRecoupTask { export interface PendingWithdrawTask { type: PendingTaskType.Withdraw; lastError: TalerErrorDetail | undefined; - retryInfo: RetryInfo; + retryInfo?: RetryInfo; withdrawalGroupId: string; }