wallet-core: fix revocation, re-introduce reserves object store

This commit is contained in:
Florian Dold 2022-08-26 01:18:01 +02:00
parent 70d0199572
commit 30e8fd83c2
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
10 changed files with 218 additions and 112 deletions

View File

@ -243,7 +243,7 @@ export async function runLibeufinBasicTest(t: GlobalTestState) {
WalletApiOperation.AcceptManualWithdrawal, WalletApiOperation.AcceptManualWithdrawal,
{ {
exchangeBaseUrl: exchange.baseUrl, exchangeBaseUrl: exchange.baseUrl,
amount: "EUR:10", amount: "EUR:15",
}, },
); );

View File

@ -17,12 +17,11 @@
/** /**
* Imports. * Imports.
*/ */
import { GlobalTestState, delayMs } from "../harness/harness.js"; import { GlobalTestState } from "../harness/harness.js";
import { import {
SandboxUserBundle, SandboxUserBundle,
NexusUserBundle, NexusUserBundle,
launchLibeufinServices, launchLibeufinServices,
LibeufinSandboxApi,
LibeufinNexusApi, LibeufinNexusApi,
} from "../harness/libeufin"; } from "../harness/libeufin";
@ -73,7 +72,7 @@ export async function runLibeufinNexusBalanceTest(t: GlobalTestState) {
user02sandbox.ebicsBankAccount.label, // debit user02sandbox.ebicsBankAccount.label, // debit
user01sandbox.ebicsBankAccount.label, // credit user01sandbox.ebicsBankAccount.label, // credit
"EUR:10", "EUR:10",
"first payment", "second payment",
); );
await LibeufinNexusApi.fetchTransactions( await LibeufinNexusApi.fetchTransactions(
@ -86,9 +85,9 @@ export async function runLibeufinNexusBalanceTest(t: GlobalTestState) {
// Check that user 01 has 20, via Nexus. // Check that user 01 has 20, via Nexus.
let accountInfo = await LibeufinNexusApi.getBankAccount( let accountInfo = await LibeufinNexusApi.getBankAccount(
libeufinServices.libeufinNexus, libeufinServices.libeufinNexus,
user01nexus.localAccountName user01nexus.localAccountName,
); );
t.assertTrue(accountInfo.data.lastSeenBalance == "EUR:20"); t.assertAmountEquals(accountInfo.data.lastSeenBalance, "EUR:20");
// user 01 gives 30 // user 01 gives 30
await libeufinServices.libeufinSandbox.makeTransaction( await libeufinServices.libeufinSandbox.makeTransaction(
@ -107,8 +106,8 @@ export async function runLibeufinNexusBalanceTest(t: GlobalTestState) {
let accountInfoDebit = await LibeufinNexusApi.getBankAccount( let accountInfoDebit = await LibeufinNexusApi.getBankAccount(
libeufinServices.libeufinNexus, libeufinServices.libeufinNexus,
user01nexus.localAccountName user01nexus.localAccountName,
); );
t.assertTrue(accountInfoDebit.data.lastSeenBalance == "-EUR:10"); t.assertDeepEqual(accountInfoDebit.data.lastSeenBalance, "-EUR:10");
} }
runLibeufinNexusBalanceTest.suites = ["libeufin"]; runLibeufinNexusBalanceTest.suites = ["libeufin"];

View File

@ -26,71 +26,71 @@ import {
TestRunResult, TestRunResult,
} from "../harness/harness.js"; } from "../harness/harness.js";
import { runAgeRestrictionsTest } from "./test-age-restrictions.js"; import { runAgeRestrictionsTest } from "./test-age-restrictions.js";
import { runBankApiTest } from "./test-bank-api"; import { runBankApiTest } from "./test-bank-api.js";
import { runClaimLoopTest } from "./test-claim-loop"; import { runClaimLoopTest } from "./test-claim-loop.js";
import { runClauseSchnorrTest } from "./test-clause-schnorr.js"; import { runClauseSchnorrTest } from "./test-clause-schnorr.js";
import { runDenomUnofferedTest } from "./test-denom-unoffered.js"; import { runDenomUnofferedTest } from "./test-denom-unoffered.js";
import { runDepositTest } from "./test-deposit"; import { runDepositTest } from "./test-deposit.js";
import { runExchangeManagementTest } from "./test-exchange-management"; import { runExchangeManagementTest } from "./test-exchange-management.js";
import { runExchangeTimetravelTest } from "./test-exchange-timetravel.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 { runForcedSelectionTest } from "./test-forced-selection.js";
import { runLibeufinApiBankaccountTest } from "./test-libeufin-api-bankaccount"; import { runLibeufinApiBankaccountTest } from "./test-libeufin-api-bankaccount.js";
import { runLibeufinApiBankconnectionTest } from "./test-libeufin-api-bankconnection"; import { runLibeufinApiBankconnectionTest } from "./test-libeufin-api-bankconnection.js";
import { runLibeufinApiFacadeTest } from "./test-libeufin-api-facade"; import { runLibeufinApiFacadeTest } from "./test-libeufin-api-facade.js";
import { runLibeufinApiFacadeBadRequestTest } from "./test-libeufin-api-facade-bad-request"; import { runLibeufinApiFacadeBadRequestTest } from "./test-libeufin-api-facade-bad-request.js";
import { runLibeufinApiPermissionsTest } from "./test-libeufin-api-permissions"; import { runLibeufinApiPermissionsTest } from "./test-libeufin-api-permissions.js";
import { runLibeufinApiSandboxCamtTest } from "./test-libeufin-api-sandbox-camt"; import { runLibeufinApiSandboxCamtTest } from "./test-libeufin-api-sandbox-camt.js";
import { runLibeufinApiSandboxTransactionsTest } from "./test-libeufin-api-sandbox-transactions"; import { runLibeufinApiSandboxTransactionsTest } from "./test-libeufin-api-sandbox-transactions.js";
import { runLibeufinApiSchedulingTest } from "./test-libeufin-api-scheduling"; import { runLibeufinApiSchedulingTest } from "./test-libeufin-api-scheduling.js";
import { runLibeufinApiUsersTest } from "./test-libeufin-api-users"; import { runLibeufinApiUsersTest } from "./test-libeufin-api-users.js";
import { runLibeufinBadGatewayTest } from "./test-libeufin-bad-gateway"; import { runLibeufinBadGatewayTest } from "./test-libeufin-bad-gateway.js";
import { runLibeufinBasicTest } from "./test-libeufin-basic"; import { runLibeufinBasicTest } from "./test-libeufin-basic.js";
import { runLibeufinC5xTest } from "./test-libeufin-c5x"; import { runLibeufinC5xTest } from "./test-libeufin-c5x.js";
import { runLibeufinAnastasisFacadeTest } from "./test-libeufin-facade-anastasis"; import { runLibeufinAnastasisFacadeTest } from "./test-libeufin-facade-anastasis.js";
import { runLibeufinKeyrotationTest } from "./test-libeufin-keyrotation"; import { runLibeufinKeyrotationTest } from "./test-libeufin-keyrotation.js";
import { runLibeufinNexusBalanceTest } from "./test-libeufin-nexus-balance"; import { runLibeufinNexusBalanceTest } from "./test-libeufin-nexus-balance.js";
import { runLibeufinRefundTest } from "./test-libeufin-refund"; import { runLibeufinRefundTest } from "./test-libeufin-refund.js";
import { runLibeufinRefundMultipleUsersTest } from "./test-libeufin-refund-multiple-users"; import { runLibeufinRefundMultipleUsersTest } from "./test-libeufin-refund-multiple-users.js";
import { runLibeufinSandboxWireTransferCliTest } from "./test-libeufin-sandbox-wire-transfer-cli"; import { runLibeufinSandboxWireTransferCliTest } from "./test-libeufin-sandbox-wire-transfer-cli.js";
import { runLibeufinTutorialTest } from "./test-libeufin-tutorial"; import { runLibeufinTutorialTest } from "./test-libeufin-tutorial.js";
import { runMerchantExchangeConfusionTest } from "./test-merchant-exchange-confusion"; import { runMerchantExchangeConfusionTest } from "./test-merchant-exchange-confusion.js";
import { runMerchantInstancesTest } from "./test-merchant-instances"; import { runMerchantInstancesTest } from "./test-merchant-instances.js";
import { runMerchantInstancesDeleteTest } from "./test-merchant-instances-delete"; import { runMerchantInstancesDeleteTest } from "./test-merchant-instances-delete";
import { runMerchantInstancesUrlsTest } from "./test-merchant-instances-urls"; import { runMerchantInstancesUrlsTest } from "./test-merchant-instances-urls.js";
import { runMerchantLongpollingTest } from "./test-merchant-longpolling"; import { runMerchantLongpollingTest } from "./test-merchant-longpolling.js";
import { runMerchantRefundApiTest } from "./test-merchant-refund-api"; import { runMerchantRefundApiTest } from "./test-merchant-refund-api.js";
import { runMerchantSpecPublicOrdersTest } from "./test-merchant-spec-public-orders.js"; import { runMerchantSpecPublicOrdersTest } from "./test-merchant-spec-public-orders.js";
import { runPayAbortTest } from "./test-pay-abort"; import { runPayAbortTest } from "./test-pay-abort.js";
import { runPayPaidTest } from "./test-pay-paid"; import { runPayPaidTest } from "./test-pay-paid.js";
import { runPaymentTest } from "./test-payment"; import { runPaymentTest } from "./test-payment.js";
import { runPaymentClaimTest } from "./test-payment-claim"; import { runPaymentClaimTest } from "./test-payment-claim.js";
import { runPaymentFaultTest } from "./test-payment-fault"; import { runPaymentFaultTest } from "./test-payment-fault.js";
import { runPaymentForgettableTest } from "./test-payment-forgettable.js"; import { runPaymentForgettableTest } from "./test-payment-forgettable.js";
import { runPaymentIdempotencyTest } from "./test-payment-idempotency"; import { runPaymentIdempotencyTest } from "./test-payment-idempotency.js";
import { runPaymentMultipleTest } from "./test-payment-multiple"; import { runPaymentMultipleTest } from "./test-payment-multiple.js";
import { runPaymentDemoTest } from "./test-payment-on-demo"; import { runPaymentDemoTest } from "./test-payment-on-demo.js";
import { runPaymentTransientTest } from "./test-payment-transient"; import { runPaymentTransientTest } from "./test-payment-transient.js";
import { runPaymentZeroTest } from "./test-payment-zero.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 { runPeerToPeerPullTest } from "./test-peer-to-peer-pull.js";
import { runPeerToPeerPushTest } from "./test-peer-to-peer-push.js"; import { runPeerToPeerPushTest } from "./test-peer-to-peer-push.js";
import { runRefundTest } from "./test-refund"; import { runRefundTest } from "./test-refund.js";
import { runRefundAutoTest } from "./test-refund-auto"; import { runRefundAutoTest } from "./test-refund-auto.js";
import { runRefundGoneTest } from "./test-refund-gone"; import { runRefundGoneTest } from "./test-refund-gone.js";
import { runRefundIncrementalTest } from "./test-refund-incremental"; import { runRefundIncrementalTest } from "./test-refund-incremental.js";
import { runRevocationTest } from "./test-revocation"; import { runRevocationTest } from "./test-revocation.js";
import { runTimetravelAutorefreshTest } from "./test-timetravel-autorefresh"; import { runTimetravelAutorefreshTest } from "./test-timetravel-autorefresh.js";
import { runTimetravelWithdrawTest } from "./test-timetravel-withdraw"; import { runTimetravelWithdrawTest } from "./test-timetravel-withdraw.js";
import { runTippingTest } from "./test-tipping"; import { runTippingTest } from "./test-tipping.js";
import { runWalletBackupBasicTest } from "./test-wallet-backup-basic"; import { runWalletBackupBasicTest } from "./test-wallet-backup-basic.js";
import { runWalletBackupDoublespendTest } from "./test-wallet-backup-doublespend"; import { runWalletBackupDoublespendTest } from "./test-wallet-backup-doublespend.js";
import { runWalletDblessTest } from "./test-wallet-dbless.js"; import { runWalletDblessTest } from "./test-wallet-dbless.js";
import { runWallettestingTest } from "./test-wallettesting"; import { runWallettestingTest } from "./test-wallettesting.js";
import { runWithdrawalAbortBankTest } from "./test-withdrawal-abort-bank"; import { runWithdrawalAbortBankTest } from "./test-withdrawal-abort-bank.js";
import { runWithdrawalBankIntegratedTest } from "./test-withdrawal-bank-integrated"; import { runWithdrawalBankIntegratedTest } from "./test-withdrawal-bank-integrated.js";
import { runWithdrawalFakebankTest } from "./test-withdrawal-fakebank.js"; import { runWithdrawalFakebankTest } from "./test-withdrawal-fakebank.js";
import { runTestWithdrawalManualTest } from "./test-withdrawal-manual"; import { runTestWithdrawalManualTest } from "./test-withdrawal-manual.js";
/** /**
* Test runner. * Test runner.

View File

@ -1224,6 +1224,7 @@ export const enum WithdrawalRecordType {
BankIntegrated = "bank-integrated", BankIntegrated = "bank-integrated",
PeerPullCredit = "peer-pull-credit", PeerPullCredit = "peer-pull-credit",
PeerPushCredit = "peer-push-credit", PeerPushCredit = "peer-push-credit",
Recoup = "recoup",
} }
export interface WgInfoBankIntegrated { export interface WgInfoBankIntegrated {
@ -1253,11 +1254,16 @@ export interface WgInfoBankPeerPush {
withdrawalType: WithdrawalRecordType.PeerPushCredit; withdrawalType: WithdrawalRecordType.PeerPushCredit;
} }
export interface WgInfoBankRecoup {
withdrawalType: WithdrawalRecordType.Recoup;
}
export type WgInfo = export type WgInfo =
| WgInfoBankIntegrated | WgInfoBankIntegrated
| WgInfoBankManual | WgInfoBankManual
| WgInfoBankPeerPull | WgInfoBankPeerPull
| WgInfoBankPeerPush; | WgInfoBankPeerPush
| WgInfoBankRecoup;
/** /**
* Group of withdrawal operations that need to be executed. * Group of withdrawal operations that need to be executed.
@ -1287,6 +1293,8 @@ export interface WithdrawalGroupRecord {
/** /**
* The reserve private key. * The reserve private key.
*
* FIXME: Already in the reserves object store, redundant!
*/ */
reservePriv: string; reservePriv: string;
@ -1355,9 +1363,9 @@ export interface WithdrawalGroupRecord {
denomSelUid: string; denomSelUid: string;
/** /**
* Retry info, always present even on completed operations so that indexing works. * Retry info.
*/ */
retryInfo: RetryInfo; retryInfo?: RetryInfo;
lastError: TalerErrorDetail | undefined; lastError: TalerErrorDetail | undefined;
} }
@ -1386,6 +1394,8 @@ export interface RecoupGroupRecord {
*/ */
recoupGroupId: string; recoupGroupId: string;
exchangeBaseUrl: string;
timestampStarted: TalerProtocolTimestamp; timestampStarted: TalerProtocolTimestamp;
timestampFinished: TalerProtocolTimestamp | undefined; timestampFinished: TalerProtocolTimestamp | undefined;
@ -1724,6 +1734,13 @@ export interface PeerPullPaymentIncomingRecord {
contractPriv: string; contractPriv: string;
} }
// FIXME: give this some smaller "row ID" to
// reference in other records?
export interface ReserveRecord {
reservePub: string;
reservePriv: string;
}
export const WalletStoresV1 = { export const WalletStoresV1 = {
coins: describeStore( coins: describeStore(
describeContents<CoinRecord>("coins", { describeContents<CoinRecord>("coins", {
@ -1735,6 +1752,12 @@ export const WalletStoresV1 = {
byCoinEvHash: describeIndex("byCoinEvHash", "coinEvHash"), byCoinEvHash: describeIndex("byCoinEvHash", "coinEvHash"),
}, },
), ),
reserves: describeStore(
describeContents<ReserveRecord>("reserves", {
keyPath: "reservePub",
}),
{},
),
config: describeStore( config: describeStore(
describeContents<ConfigRecord>("config", { keyPath: "key" }), describeContents<ConfigRecord>("config", { keyPath: "key" }),
{}, {},

View File

@ -73,7 +73,6 @@ export interface MerchantOperations {
): Promise<MerchantInfo>; ): Promise<MerchantInfo>;
} }
/** /**
* Interface for exchange-related operations. * Interface for exchange-related operations.
*/ */
@ -113,6 +112,7 @@ export interface RecoupOperations {
refreshGroups: typeof WalletStoresV1.refreshGroups; refreshGroups: typeof WalletStoresV1.refreshGroups;
coins: typeof WalletStoresV1.coins; coins: typeof WalletStoresV1.coins;
}>, }>,
exchangeBaseUrl: string,
coinPubs: string[], coinPubs: string[],
): Promise<string>; ): Promise<string>;
processRecoupGroup( processRecoupGroup(

View File

@ -743,6 +743,7 @@ async function updateExchangeFromUrlImpl(
recoupGroupId = await ws.recoupOps.createRecoupGroup( recoupGroupId = await ws.recoupOps.createRecoupGroup(
ws, ws,
tx, tx,
exchange.baseUrl,
newlyRevokedCoinPubs, newlyRevokedCoinPubs,
); );
} }

View File

@ -126,7 +126,7 @@ async function gatherWithdrawalPending(
resp.pendingOperations.push({ resp.pendingOperations.push({
type: PendingTaskType.Withdraw, type: PendingTaskType.Withdraw,
givesLifeness: true, givesLifeness: true,
timestampDue: wsr.retryInfo.nextRetry, timestampDue: wsr.retryInfo?.nextRetry ?? AbsoluteTime.now(),
withdrawalGroupId: wsr.withdrawalGroupId, withdrawalGroupId: wsr.withdrawalGroupId,
lastError: wsr.lastError, lastError: wsr.lastError,
retryInfo: wsr.retryInfo, retryInfo: wsr.retryInfo,

View File

@ -36,16 +36,17 @@ import {
TalerErrorDetail, TalerErrorDetail,
TalerProtocolTimestamp, TalerProtocolTimestamp,
URL, URL,
codecForReserveStatus,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { import {
CoinRecord, CoinRecord,
CoinSourceType, CoinSourceType,
CoinStatus, CoinStatus,
OperationStatus,
RecoupGroupRecord, RecoupGroupRecord,
RefreshCoinSource, RefreshCoinSource,
ReserveRecordStatus, ReserveRecordStatus,
WalletStoresV1, WalletStoresV1,
WithdrawalRecordType,
WithdrawCoinSource, WithdrawCoinSource,
} from "../db.js"; } from "../db.js";
import { InternalWalletState } from "../internal-wallet-state.js"; import { InternalWalletState } from "../internal-wallet-state.js";
@ -109,6 +110,10 @@ async function reportRecoupError(
ws.notify({ type: NotificationType.RecoupOperationError, error: err }); 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( async function putGroupAsFinished(
ws: InternalWalletState, ws: InternalWalletState,
tx: GetReadWriteAccess<{ tx: GetReadWriteAccess<{
@ -127,29 +132,6 @@ async function putGroupAsFinished(
return; return;
} }
recoupGroup.recoupFinishedPerCoin[coinIdx] = true; 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); await tx.recoupGroups.put(recoupGroup);
} }
@ -258,8 +240,6 @@ async function recoupWithdrawCoin(
const currency = updatedCoin.currentAmount.currency; const currency = updatedCoin.currentAmount.currency;
updatedCoin.currentAmount = Amounts.getZero(currency); updatedCoin.currentAmount = Amounts.getZero(currency);
await tx.coins.put(updatedCoin); await tx.coins.put(updatedCoin);
// FIXME: Actually withdraw here!
// await internalCreateWithdrawalGroup(ws, {...});
await putGroupAsFinished(ws, tx, recoupGroup, coinIdx); await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
}); });
@ -392,7 +372,7 @@ async function processRecoupGroupImpl(
): Promise<void> { ): Promise<void> {
const forceNow = options.forceNow ?? false; const forceNow = options.forceNow ?? false;
await setupRecoupRetry(ws, recoupGroupId, { reset: forceNow }); await setupRecoupRetry(ws, recoupGroupId, { reset: forceNow });
const recoupGroup = await ws.db let recoupGroup = await ws.db
.mktx((x) => ({ .mktx((x) => ({
recoupGroups: x.recoupGroups, recoupGroups: x.recoupGroups,
})) }))
@ -416,23 +396,105 @@ async function processRecoupGroupImpl(
}); });
await Promise.all(ps); await Promise.all(ps);
const reserveSet = new Set<string>(); recoupGroup = await ws.db
for (let i = 0; i < recoupGroup.coinPubs.length; i++) { .mktx((x) => ({
const coinPub = recoupGroup.coinPubs[i]; recoupGroups: x.recoupGroups,
const coin = await ws.db }))
.mktx((x) => ({ .runReadOnly(async (tx) => {
coins: x.coins, return tx.recoupGroups.get(recoupGroupId);
})) });
.runReadOnly(async (tx) => { if (!recoupGroup) {
return tx.coins.get(coinPub); return;
}); }
if (!coin) {
throw Error(`Coin ${coinPub} not found, can't request recoup`); for (const b of recoupGroup.recoupFinishedPerCoin) {
} if (!b) {
if (coin.coinSource.type === CoinSourceType.Withdraw) { return;
reserveSet.add(coin.coinSource.reservePub);
} }
} }
logger.info("all recoups of recoup group are finished");
const reserveSet = new Set<string>();
const reservePrivMap: Record<string, string> = {};
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( export async function createRecoupGroup(
@ -443,12 +505,14 @@ export async function createRecoupGroup(
refreshGroups: typeof WalletStoresV1.refreshGroups; refreshGroups: typeof WalletStoresV1.refreshGroups;
coins: typeof WalletStoresV1.coins; coins: typeof WalletStoresV1.coins;
}>, }>,
exchangeBaseUrl: string,
coinPubs: string[], coinPubs: string[],
): Promise<string> { ): Promise<string> {
const recoupGroupId = encodeCrock(getRandomBytes(32)); const recoupGroupId = encodeCrock(getRandomBytes(32));
const recoupGroup: RecoupGroupRecord = { const recoupGroup: RecoupGroupRecord = {
recoupGroupId, recoupGroupId,
exchangeBaseUrl: exchangeBaseUrl,
coinPubs: coinPubs, coinPubs: coinPubs,
lastError: undefined, lastError: undefined,
timestampFinished: undefined, timestampFinished: undefined,

View File

@ -1135,6 +1135,22 @@ async function processWithdrawGroupImpl(
withdrawalGroup.exchangeBaseUrl, 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 const numTotalCoins = withdrawalGroup.denomsSel.selectedDenoms
.map((x) => x.count) .map((x) => x.count)
.reduce((a, b) => a + b); .reduce((a, b) => a + b);
@ -1709,7 +1725,6 @@ export async function internalCreateWithdrawalGroup(
args: { args: {
reserveStatus: ReserveRecordStatus; reserveStatus: ReserveRecordStatus;
amount: AmountJson; amount: AmountJson;
bankInfo?: ReserveBankInfo;
exchangeBaseUrl: string; exchangeBaseUrl: string;
forcedDenomSel?: ForcedDenomSel; forcedDenomSel?: ForcedDenomSel;
reserveKeyPair?: EddsaKeypair; reserveKeyPair?: EddsaKeypair;
@ -1776,12 +1791,17 @@ export async function internalCreateWithdrawalGroup(
await ws.db await ws.db
.mktx((x) => ({ .mktx((x) => ({
withdrawalGroups: x.withdrawalGroups, withdrawalGroups: x.withdrawalGroups,
reserves: x.reserves,
exchanges: x.exchanges, exchanges: x.exchanges,
exchangeDetails: x.exchangeDetails, exchangeDetails: x.exchangeDetails,
exchangeTrust: x.exchangeTrust, exchangeTrust: x.exchangeTrust,
})) }))
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
await tx.withdrawalGroups.add(withdrawalGroup); await tx.withdrawalGroups.add(withdrawalGroup);
await tx.reserves.put({
reservePub: withdrawalGroup.reservePub,
reservePriv: withdrawalGroup.reservePriv,
});
if (!isAudited && !isTrusted) { if (!isAudited && !isTrusted) {
await tx.exchangeTrust.put({ await tx.exchangeTrust.put({
@ -1906,7 +1926,6 @@ export async function createManualWithdrawal(
withdrawalType: WithdrawalRecordType.BankManual, withdrawalType: WithdrawalRecordType.BankManual,
}, },
exchangeBaseUrl: req.exchangeBaseUrl, exchangeBaseUrl: req.exchangeBaseUrl,
bankInfo: undefined,
forcedDenomSel: req.forcedDenomSel, forcedDenomSel: req.forcedDenomSel,
restrictAge: req.restrictAge, restrictAge: req.restrictAge,
reserveStatus: ReserveRecordStatus.QueryingStatus, reserveStatus: ReserveRecordStatus.QueryingStatus,

View File

@ -182,7 +182,7 @@ export interface PendingRecoupTask {
export interface PendingWithdrawTask { export interface PendingWithdrawTask {
type: PendingTaskType.Withdraw; type: PendingTaskType.Withdraw;
lastError: TalerErrorDetail | undefined; lastError: TalerErrorDetail | undefined;
retryInfo: RetryInfo; retryInfo?: RetryInfo;
withdrawalGroupId: string; withdrawalGroupId: string;
} }