diff options
author | Florian Dold <florian@dold.me> | 2022-08-26 01:18:01 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2022-08-26 01:18:01 +0200 |
commit | 30e8fd83c256826fc995edae499bf8bb6b60b7f2 (patch) | |
tree | 994752f4eced988458cf2b9ae3f1c143ca73cdf5 /packages/taler-wallet-core/src | |
parent | 70d0199572ee6a95c68dd0b960d80e4ae93c4b0a (diff) | |
download | wallet-core-30e8fd83c256826fc995edae499bf8bb6b60b7f2.tar.xz |
wallet-core: fix revocation, re-introduce reserves object store
Diffstat (limited to 'packages/taler-wallet-core/src')
-rw-r--r-- | packages/taler-wallet-core/src/db.ts | 29 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/internal-wallet-state.ts | 2 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/operations/exchanges.ts | 1 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/operations/pending.ts | 2 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/operations/recoup.ts | 134 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/operations/withdraw.ts | 23 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/pending-types.ts | 2 |
7 files changed, 150 insertions, 43 deletions
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<CoinRecord>("coins", { @@ -1735,6 +1752,12 @@ export const WalletStoresV1 = { byCoinEvHash: describeIndex("byCoinEvHash", "coinEvHash"), }, ), + reserves: describeStore( + describeContents<ReserveRecord>("reserves", { + keyPath: "reservePub", + }), + {}, + ), config: describeStore( describeContents<ConfigRecord>("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<MerchantInfo>; } - /** * 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<string>; 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<void> { 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); + 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<string>(); + const reservePrivMap: Record<string, string> = {}; for (let i = 0; i < recoupGroup.coinPubs.length; i++) { const coinPub = recoupGroup.coinPubs[i]; - const coin = await ws.db + await ws.db .mktx((x) => ({ coins: x.coins, + reserves: x.reserves, })) .runReadOnly(async (tx) => { - return tx.coins.get(coinPub); + 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; + } }); - if (!coin) { - throw Error(`Coin ${coinPub} not found, can't request recoup`); - } - if (coin.coinSource.type === CoinSourceType.Withdraw) { - reserveSet.add(coin.coinSource.reservePub); - } } + + 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<string> { 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; } |