wallet-core: do not rely on reserve history for withdrawals

This commit is contained in:
Florian Dold 2022-03-10 16:30:24 +01:00
parent 1607c728bc
commit 9d66078852
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
11 changed files with 194 additions and 130 deletions

View File

@ -43,15 +43,9 @@ export interface ReserveStatus {
* Balance left in the reserve. * Balance left in the reserve.
*/ */
balance: AmountString; balance: AmountString;
/**
* Transaction history for the reserve.
*/
history: ReserveTransaction[];
} }
export const codecForReserveStatus = (): Codec<ReserveStatus> => export const codecForReserveStatus = (): Codec<ReserveStatus> =>
buildCodecForObject<ReserveStatus>() buildCodecForObject<ReserveStatus>()
.property("balance", codecForString()) .property("balance", codecForString())
.property("history", codecForList(codecForReserveTransaction()))
.build("ReserveStatus"); .build("ReserveStatus");

View File

@ -597,9 +597,6 @@ export interface PlanchetRecord {
denomPubHash: string; denomPubHash: string;
// FIXME: maybe too redundant?
denomPub: DenominationPubKey;
blindingKey: string; blindingKey: string;
withdrawSig: string; withdrawSig: string;
@ -607,10 +604,6 @@ export interface PlanchetRecord {
coinEv: CoinEnvelope; coinEv: CoinEnvelope;
coinEvHash: string; coinEvHash: string;
coinValue: AmountJson;
isFromTip: boolean;
} }
/** /**
@ -685,11 +678,6 @@ export interface CoinRecord {
*/ */
coinPriv: string; coinPriv: string;
/**
* Key used by the exchange used to sign the coin.
*/
denomPub: DenominationPubKey;
/** /**
* Hash of the public key that signs the coin. * Hash of the public key that signs the coin.
*/ */
@ -1378,6 +1366,8 @@ export interface WithdrawalGroupRecord {
/** /**
* When was the withdrawal operation completed? * When was the withdrawal operation completed?
*
* FIXME: We should probably drop this and introduce an OperationStatus field.
*/ */
timestampFinish?: Timestamp; timestampFinish?: Timestamp;

View File

@ -419,7 +419,6 @@ export async function importBackup(
coinPub: compCoin.coinPub, coinPub: compCoin.coinPub,
suspended: false, suspended: false,
exchangeBaseUrl: backupExchangeDetails.base_url, exchangeBaseUrl: backupExchangeDetails.base_url,
denomPub: backupDenomination.denom_pub,
denomPubHash, denomPubHash,
status: backupCoin.fresh status: backupCoin.fresh
? CoinStatus.Fresh ? CoinStatus.Fresh

View File

@ -315,7 +315,7 @@ export async function getCandidatePayCoins(
candidateCoins.push({ candidateCoins.push({
availableAmount: coin.currentAmount, availableAmount: coin.currentAmount,
coinPub: coin.coinPub, coinPub: coin.coinPub,
denomPub: coin.denomPub, denomPub: denom.denomPub,
feeDeposit: denom.feeDeposit, feeDeposit: denom.feeDeposit,
exchangeBaseUrl: denom.exchangeBaseUrl, exchangeBaseUrl: denom.exchangeBaseUrl,
}); });
@ -1397,7 +1397,7 @@ export async function generateDepositPermissions(
coinPub: coin.coinPub, coinPub: coin.coinPub,
contractTermsHash: contractData.contractTermsHash, contractTermsHash: contractData.contractTermsHash,
denomPubHash: coin.denomPubHash, denomPubHash: coin.denomPubHash,
denomKeyType: coin.denomPub.cipher, denomKeyType: denom.denomPub.cipher,
denomSig: coin.denomSig, denomSig: coin.denomSig,
exchangeBaseUrl: coin.exchangeBaseUrl, exchangeBaseUrl: coin.exchangeBaseUrl,
feeDeposit: denom.feeDeposit, feeDeposit: denom.feeDeposit,

View File

@ -164,18 +164,34 @@ async function recoupWithdrawCoin(
cs: WithdrawCoinSource, cs: WithdrawCoinSource,
): Promise<void> { ): Promise<void> {
const reservePub = cs.reservePub; const reservePub = cs.reservePub;
const reserve = await ws.db const d = await ws.db
.mktx((x) => ({ .mktx((x) => ({
reserves: x.reserves, reserves: x.reserves,
denominations: x.denominations,
})) }))
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
return tx.reserves.get(reservePub); const reserve = await tx.reserves.get(reservePub);
if (!reserve) {
return;
}
const denomInfo = await ws.getDenomInfo(
ws,
tx,
reserve.exchangeBaseUrl,
coin.denomPubHash,
);
if (!denomInfo) {
return;
}
return { reserve, denomInfo };
}); });
if (!reserve) { if (!d) {
// FIXME: We should at least emit some pending operation / warning for this? // FIXME: We should at least emit some pending operation / warning for this?
return; return;
} }
const { reserve, denomInfo } = d;
ws.notify({ ws.notify({
type: NotificationType.RecoupStarted, type: NotificationType.RecoupStarted,
}); });
@ -184,7 +200,7 @@ async function recoupWithdrawCoin(
blindingKey: coin.blindingKey, blindingKey: coin.blindingKey,
coinPriv: coin.coinPriv, coinPriv: coin.coinPriv,
coinPub: coin.coinPub, coinPub: coin.coinPub,
denomPub: coin.denomPub, denomPub: denomInfo.denomPub,
denomPubHash: coin.denomPubHash, denomPubHash: coin.denomPubHash,
denomSig: coin.denomSig, denomSig: coin.denomSig,
}); });
@ -253,6 +269,28 @@ async function recoupRefreshCoin(
coin: CoinRecord, coin: CoinRecord,
cs: RefreshCoinSource, cs: RefreshCoinSource,
): Promise<void> { ): Promise<void> {
const d = await ws.db
.mktx((x) => ({
coins: x.coins,
denominations: x.denominations,
}))
.runReadOnly(async (tx) => {
const denomInfo = await ws.getDenomInfo(
ws,
tx,
coin.exchangeBaseUrl,
coin.denomPubHash,
);
if (!denomInfo) {
return;
}
return { denomInfo };
});
if (!d) {
// FIXME: We should at least emit some pending operation / warning for this?
return;
}
ws.notify({ ws.notify({
type: NotificationType.RecoupStarted, type: NotificationType.RecoupStarted,
}); });
@ -261,7 +299,7 @@ async function recoupRefreshCoin(
blindingKey: coin.blindingKey, blindingKey: coin.blindingKey,
coinPriv: coin.coinPriv, coinPriv: coin.coinPriv,
coinPub: coin.coinPub, coinPub: coin.coinPub,
denomPub: coin.denomPub, denomPub: d.denomInfo.denomPub,
denomPubHash: coin.denomPubHash, denomPubHash: coin.denomPubHash,
denomSig: coin.denomSig, denomSig: coin.denomSig,
}); });

View File

@ -395,7 +395,7 @@ async function refreshMelt(
oldCoin.exchangeBaseUrl, oldCoin.exchangeBaseUrl,
); );
let meltReqBody: any; let meltReqBody: any;
if (oldCoin.denomPub.cipher === DenomKeyType.Rsa) { if (oldDenom.denomPub.cipher === DenomKeyType.Rsa) {
meltReqBody = { meltReqBody = {
coin_pub: oldCoin.coinPub, coin_pub: oldCoin.coinPub,
confirm_sig: derived.confirmSig, confirm_sig: derived.confirmSig,
@ -671,7 +671,6 @@ async function refreshReveal(
coinPriv: pc.coinPriv, coinPriv: pc.coinPriv,
coinPub: pc.coinPub, coinPub: pc.coinPub,
currentAmount: denom.value, currentAmount: denom.value,
denomPub: denom.denomPub,
denomPubHash: denom.denomPubHash, denomPubHash: denom.denomPubHash,
denomSig: { denomSig: {
cipher: DenomKeyType.Rsa, cipher: DenomKeyType.Rsa,

View File

@ -587,8 +587,8 @@ async function updateReserve(
logger.trace(`got reserve status ${j2s(result.response)}`); logger.trace(`got reserve status ${j2s(result.response)}`);
const reserveInfo = result.response; const reserveInfo = result.response;
const balance = Amounts.parseOrThrow(reserveInfo.balance); const reserveBalance = Amounts.parseOrThrow(reserveInfo.balance);
const currency = balance.currency; const currency = reserveBalance.currency;
await updateWithdrawalDenoms(ws, reserve.exchangeBaseUrl); await updateWithdrawalDenoms(ws, reserve.exchangeBaseUrl);
const denoms = await getCandidateWithdrawalDenoms( const denoms = await getCandidateWithdrawalDenoms(
@ -598,73 +598,50 @@ async function updateReserve(
const newWithdrawalGroup = await ws.db const newWithdrawalGroup = await ws.db
.mktx((x) => ({ .mktx((x) => ({
coins: x.coins,
planchets: x.planchets, planchets: x.planchets,
withdrawalGroups: x.withdrawalGroups, withdrawalGroups: x.withdrawalGroups,
reserves: x.reserves, reserves: x.reserves,
denominations: x.denominations,
})) }))
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
const newReserve = await tx.reserves.get(reserve.reservePub); const newReserve = await tx.reserves.get(reserve.reservePub);
if (!newReserve) { if (!newReserve) {
return; return;
} }
let amountReservePlus = Amounts.getZero(currency); let amountReservePlus = reserveBalance;
let amountReserveMinus = Amounts.getZero(currency); let amountReserveMinus = Amounts.getZero(currency);
// Subtract withdrawal groups for this reserve from the available amount. // Subtract amount allocated in unfinished withdrawal groups
// for this reserve from the available amount.
await tx.withdrawalGroups.indexes.byReservePub await tx.withdrawalGroups.indexes.byReservePub
.iter(reservePub) .iter(reservePub)
.forEach((wg) => { .forEachAsync(async (wg) => {
const cost = wg.denomsSel.totalWithdrawCost; if (wg.timestampFinish) {
amountReserveMinus = Amounts.add(amountReserveMinus, cost).amount; return;
});
for (const entry of reserveInfo.history) {
switch (entry.type) {
case ReserveTransactionType.Credit:
amountReservePlus = Amounts.add(
amountReservePlus,
Amounts.parseOrThrow(entry.amount),
).amount;
break;
case ReserveTransactionType.Recoup:
amountReservePlus = Amounts.add(
amountReservePlus,
Amounts.parseOrThrow(entry.amount),
).amount;
break;
case ReserveTransactionType.Closing:
amountReserveMinus = Amounts.add(
amountReserveMinus,
Amounts.parseOrThrow(entry.amount),
).amount;
break;
case ReserveTransactionType.Withdraw: {
// Now we check if the withdrawal transaction
// is part of any withdrawal known to this wallet.
const planchet = await tx.planchets.indexes.byCoinEvHash.get(
entry.h_coin_envelope,
);
if (planchet) {
// Amount is already accounted in some withdrawal session
break;
}
const coin = await tx.coins.indexes.byCoinEvHash.get(
entry.h_coin_envelope,
);
if (coin) {
// Amount is already accounted in some withdrawal session
break;
}
// Amount has been claimed by some withdrawal we don't know about
amountReserveMinus = Amounts.add(
amountReserveMinus,
Amounts.parseOrThrow(entry.amount),
).amount;
break;
} }
} await tx.planchets.indexes.byGroup
} .iter(wg.withdrawalGroupId)
.forEachAsync(async (pr) => {
if (pr.withdrawalDone) {
return;
}
const denomInfo = await ws.getDenomInfo(
ws,
tx,
wg.exchangeBaseUrl,
pr.denomPubHash,
);
if (!denomInfo) {
logger.error(`no denom info found for ${pr.denomPubHash}`);
return;
}
amountReserveMinus = Amounts.add(
amountReserveMinus,
denomInfo.value,
denomInfo.feeWithdraw,
).amount;
});
});
const remainingAmount = Amounts.sub( const remainingAmount = Amounts.sub(
amountReservePlus, amountReservePlus,

View File

@ -374,7 +374,6 @@ async function processTipImpl(
walletTipId: walletTipId, walletTipId: walletTipId,
}, },
currentAmount: denom.value, currentAmount: denom.value,
denomPub: denom.denomPub,
denomPubHash: denom.denomPubHash, denomPubHash: denom.denomPubHash,
denomSig: { cipher: DenomKeyType.Rsa, rsa_signature: denomSigRsa }, denomSig: { cipher: DenomKeyType.Rsa, rsa_signature: denomSigRsa },
exchangeBaseUrl: tipRecord.exchangeBaseUrl, exchangeBaseUrl: tipRecord.exchangeBaseUrl,

View File

@ -418,10 +418,7 @@ async function processPlanchetGenerate(
coinIdx, coinIdx,
coinPriv: r.coinPriv, coinPriv: r.coinPriv,
coinPub: r.coinPub, coinPub: r.coinPub,
coinValue: r.coinValue,
denomPub: r.denomPub,
denomPubHash: r.denomPubHash, denomPubHash: r.denomPubHash,
isFromTip: false,
reservePub: r.reservePub, reservePub: r.reservePub,
withdrawalDone: false, withdrawalDone: false,
withdrawSig: r.withdrawSig, withdrawSig: r.withdrawSig,
@ -557,6 +554,7 @@ async function processPlanchetVerifyAndStoreCoin(
.mktx((x) => ({ .mktx((x) => ({
withdrawalGroups: x.withdrawalGroups, withdrawalGroups: x.withdrawalGroups,
planchets: x.planchets, planchets: x.planchets,
denominations: x.denominations,
})) }))
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
let planchet = await tx.planchets.indexes.byGroupAndIndex.get([ let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
@ -570,16 +568,29 @@ async function processPlanchetVerifyAndStoreCoin(
logger.warn("processPlanchet: planchet already withdrawn"); logger.warn("processPlanchet: planchet already withdrawn");
return; return;
} }
return { planchet, exchangeBaseUrl: withdrawalGroup.exchangeBaseUrl }; const denomInfo = await ws.getDenomInfo(
ws,
tx,
withdrawalGroup.exchangeBaseUrl,
planchet.denomPubHash,
);
if (!denomInfo) {
return;
}
return {
planchet,
denomInfo,
exchangeBaseUrl: withdrawalGroup.exchangeBaseUrl,
};
}); });
if (!d) { if (!d) {
return; return;
} }
const { planchet, exchangeBaseUrl } = d; const { planchet, denomInfo } = d;
const planchetDenomPub = planchet.denomPub; const planchetDenomPub = denomInfo.denomPub;
if (planchetDenomPub.cipher !== DenomKeyType.Rsa) { if (planchetDenomPub.cipher !== DenomKeyType.Rsa) {
throw Error(`cipher (${planchetDenomPub.cipher}) not supported`); throw Error(`cipher (${planchetDenomPub.cipher}) not supported`);
} }
@ -623,9 +634,9 @@ async function processPlanchetVerifyAndStoreCoin(
} }
let denomSig: UnblindedSignature; let denomSig: UnblindedSignature;
if (planchet.denomPub.cipher === DenomKeyType.Rsa) { if (planchetDenomPub.cipher === DenomKeyType.Rsa) {
denomSig = { denomSig = {
cipher: planchet.denomPub.cipher, cipher: planchetDenomPub.cipher,
rsa_signature: denomSigRsa, rsa_signature: denomSigRsa,
}; };
} else { } else {
@ -636,12 +647,11 @@ async function processPlanchetVerifyAndStoreCoin(
blindingKey: planchet.blindingKey, blindingKey: planchet.blindingKey,
coinPriv: planchet.coinPriv, coinPriv: planchet.coinPriv,
coinPub: planchet.coinPub, coinPub: planchet.coinPub,
currentAmount: planchet.coinValue, currentAmount: denomInfo.value,
denomPub: planchet.denomPub,
denomPubHash: planchet.denomPubHash, denomPubHash: planchet.denomPubHash,
denomSig, denomSig,
coinEvHash: planchet.coinEvHash, coinEvHash: planchet.coinEvHash,
exchangeBaseUrl: exchangeBaseUrl, exchangeBaseUrl: d.exchangeBaseUrl,
status: CoinStatus.Fresh, status: CoinStatus.Fresh,
coinSource: { coinSource: {
type: CoinSourceType.Withdraw, type: CoinSourceType.Withdraw,

View File

@ -77,6 +77,8 @@ export interface AvailableCoinInfo {
/** /**
* Coin's denomination public key. * Coin's denomination public key.
*
* FIXME: We should only need the denomPubHash here, if at all.
*/ */
denomPub: DenominationPubKey; denomPub: DenominationPubKey;

View File

@ -24,23 +24,62 @@
*/ */
import { import {
AcceptManualWithdrawalResult, AcceptManualWithdrawalResult,
AcceptWithdrawalResponse, AmountJson, Amounts, BalancesResponse, codecForAbortPayWithRefundRequest, AcceptWithdrawalResponse,
AmountJson,
Amounts,
BalancesResponse,
codecForAbortPayWithRefundRequest,
codecForAcceptBankIntegratedWithdrawalRequest, codecForAcceptBankIntegratedWithdrawalRequest,
codecForAcceptExchangeTosRequest, codecForAcceptExchangeTosRequest,
codecForAcceptManualWithdrawalRequet, codecForAcceptManualWithdrawalRequet,
codecForAcceptTipRequest, codecForAcceptTipRequest,
codecForAddExchangeRequest, codecForAny, codecForApplyRefundRequest, codecForAddExchangeRequest,
codecForAny,
codecForApplyRefundRequest,
codecForConfirmPayRequest, codecForConfirmPayRequest,
codecForCreateDepositGroupRequest, codecForDeleteTransactionRequest, codecForForceRefreshRequest, codecForCreateDepositGroupRequest,
codecForGetExchangeTosRequest, codecForGetExchangeWithdrawalInfo, codecForGetFeeForDeposit, codecForGetWithdrawalDetailsForAmountRequest, codecForDeleteTransactionRequest,
codecForGetWithdrawalDetailsForUri, codecForImportDbRequest, codecForIntegrationTestArgs, codecForListKnownBankAccounts, codecForPreparePayRequest, codecForForceRefreshRequest,
codecForPrepareTipRequest, codecForRetryTransactionRequest, codecForSetCoinSuspendedRequest, codecForSetWalletDeviceIdRequest, codecForTestPayArgs, codecForGetExchangeTosRequest,
codecForTrackDepositGroupRequest, codecForTransactionsRequest, codecForWithdrawFakebankRequest, codecForWithdrawTestBalance, CoinDumpJson, CoreApiResponse, durationFromSpec, codecForGetExchangeWithdrawalInfo,
durationMin, ExchangeListItem, codecForGetFeeForDeposit,
ExchangesListRespose, getDurationRemaining, GetExchangeTosResult, isTimestampExpired, codecForGetWithdrawalDetailsForAmountRequest,
j2s, KnownBankAccounts, Logger, ManualWithdrawalDetails, NotificationType, parsePaytoUri, PaytoUri, RefreshReason, TalerErrorCode, codecForGetWithdrawalDetailsForUri,
codecForImportDbRequest,
codecForIntegrationTestArgs,
codecForListKnownBankAccounts,
codecForPreparePayRequest,
codecForPrepareTipRequest,
codecForRetryTransactionRequest,
codecForSetCoinSuspendedRequest,
codecForSetWalletDeviceIdRequest,
codecForTestPayArgs,
codecForTrackDepositGroupRequest,
codecForTransactionsRequest,
codecForWithdrawFakebankRequest,
codecForWithdrawTestBalance,
CoinDumpJson,
CoreApiResponse,
durationFromSpec,
durationMin,
ExchangeListItem,
ExchangesListRespose,
getDurationRemaining,
GetExchangeTosResult,
isTimestampExpired,
j2s,
KnownBankAccounts,
Logger,
ManualWithdrawalDetails,
NotificationType,
parsePaytoUri,
PaytoUri,
RefreshReason,
TalerErrorCode,
Timestamp, Timestamp,
timestampMin, URL, WalletNotification timestampMin,
URL,
WalletNotification,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { import {
DenomInfo, DenomInfo,
@ -50,7 +89,7 @@ import {
MerchantOperations, MerchantOperations,
NotificationListener, NotificationListener,
RecoupOperations, RecoupOperations,
ReserveOperations ReserveOperations,
} from "./common.js"; } from "./common.js";
import { CryptoApi, CryptoWorkerFactory } from "./crypto/workers/cryptoApi.js"; import { CryptoApi, CryptoWorkerFactory } from "./crypto/workers/cryptoApi.js";
import { import {
@ -59,12 +98,12 @@ import {
exportDb, exportDb,
importDb, importDb,
ReserveRecordStatus, ReserveRecordStatus,
WalletStoresV1 WalletStoresV1,
} from "./db.js"; } from "./db.js";
import { import {
makeErrorDetails, makeErrorDetails,
OperationFailedAndReportedError, OperationFailedAndReportedError,
OperationFailedError OperationFailedError,
} from "./errors.js"; } from "./errors.js";
import { exportBackup } from "./operations/backup/export.js"; import { exportBackup } from "./operations/backup/export.js";
import { import {
@ -77,7 +116,7 @@ import {
loadBackupRecovery, loadBackupRecovery,
processBackupForProvider, processBackupForProvider,
removeBackupProvider, removeBackupProvider,
runBackupCycle runBackupCycle,
} from "./operations/backup/index.js"; } from "./operations/backup/index.js";
import { setWalletDeviceId } from "./operations/backup/state.js"; import { setWalletDeviceId } from "./operations/backup/state.js";
import { getBalances } from "./operations/balance.js"; import { getBalances } from "./operations/balance.js";
@ -85,7 +124,7 @@ import {
createDepositGroup, createDepositGroup,
getFeeForDeposit, getFeeForDeposit,
processDepositGroup, processDepositGroup,
trackDepositGroup trackDepositGroup,
} from "./operations/deposits.js"; } from "./operations/deposits.js";
import { import {
acceptExchangeTermsOfService, acceptExchangeTermsOfService,
@ -94,62 +133,64 @@ import {
getExchangeRequestTimeout, getExchangeRequestTimeout,
getExchangeTrust, getExchangeTrust,
updateExchangeFromUrl, updateExchangeFromUrl,
updateExchangeTermsOfService updateExchangeTermsOfService,
} from "./operations/exchanges.js"; } from "./operations/exchanges.js";
import { getMerchantInfo } from "./operations/merchants.js"; import { getMerchantInfo } from "./operations/merchants.js";
import { import {
confirmPay, confirmPay,
preparePayForUri, preparePayForUri,
processDownloadProposal, processDownloadProposal,
processPurchasePay processPurchasePay,
} from "./operations/pay.js"; } from "./operations/pay.js";
import { getPendingOperations } from "./operations/pending.js"; import { getPendingOperations } from "./operations/pending.js";
import { createRecoupGroup, processRecoupGroup } from "./operations/recoup.js"; import { createRecoupGroup, processRecoupGroup } from "./operations/recoup.js";
import { import {
autoRefresh, autoRefresh,
createRefreshGroup, createRefreshGroup,
processRefreshGroup processRefreshGroup,
} from "./operations/refresh.js"; } from "./operations/refresh.js";
import { import {
abortFailedPayWithRefund, abortFailedPayWithRefund,
applyRefund, applyRefund,
processPurchaseQueryRefund processPurchaseQueryRefund,
} from "./operations/refund.js"; } from "./operations/refund.js";
import { import {
createReserve, createReserve,
createTalerWithdrawReserve, createTalerWithdrawReserve,
getFundingPaytoUris, getFundingPaytoUris,
processReserve processReserve,
} from "./operations/reserves.js"; } from "./operations/reserves.js";
import { import {
runIntegrationTest, runIntegrationTest,
testPay, testPay,
withdrawTestBalance withdrawTestBalance,
} from "./operations/testing.js"; } from "./operations/testing.js";
import { acceptTip, prepareTip, processTip } from "./operations/tip.js"; import { acceptTip, prepareTip, processTip } from "./operations/tip.js";
import { import {
deleteTransaction, deleteTransaction,
getTransactions, getTransactions,
retryTransaction retryTransaction,
} from "./operations/transactions.js"; } from "./operations/transactions.js";
import { import {
getExchangeWithdrawalInfo, getExchangeWithdrawalInfo,
getWithdrawalDetailsForUri, getWithdrawalDetailsForUri,
processWithdrawGroup processWithdrawGroup,
} from "./operations/withdraw.js"; } from "./operations/withdraw.js";
import { import {
PendingOperationsResponse, PendingTaskInfo, PendingTaskType PendingOperationsResponse,
PendingTaskInfo,
PendingTaskType,
} from "./pending-types.js"; } from "./pending-types.js";
import { assertUnreachable } from "./util/assertUnreachable.js"; import { assertUnreachable } from "./util/assertUnreachable.js";
import { AsyncOpMemoMap, AsyncOpMemoSingle } from "./util/asyncMemo.js"; import { AsyncOpMemoMap, AsyncOpMemoSingle } from "./util/asyncMemo.js";
import { import {
HttpRequestLibrary, HttpRequestLibrary,
readSuccessResponseJsonOrThrow readSuccessResponseJsonOrThrow,
} from "./util/http.js"; } from "./util/http.js";
import { import {
AsyncCondition, AsyncCondition,
OpenedPromise, OpenedPromise,
openPromise openPromise,
} from "./util/promiseUtils.js"; } from "./util/promiseUtils.js";
import { DbAccess, GetReadWriteAccess } from "./util/query.js"; import { DbAccess, GetReadWriteAccess } from "./util/query.js";
import { TimerGroup } from "./util/timer.js"; import { TimerGroup } from "./util/timer.js";
@ -455,7 +496,10 @@ async function getExchangeTos(
) { ) {
throw Error("exchange is in invalid state"); throw Error("exchange is in invalid state");
} }
if (acceptedFormat && acceptedFormat.findIndex(f => f === contentType) !== -1) { if (
acceptedFormat &&
acceptedFormat.findIndex((f) => f === contentType) !== -1
) {
return { return {
acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag, acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag,
currentEtag, currentEtag,
@ -464,7 +508,12 @@ async function getExchangeTos(
}; };
} }
const tosDownload = await downloadTosFromAcceptedFormat(ws, exchangeBaseUrl, getExchangeRequestTimeout(), acceptedFormat); const tosDownload = await downloadTosFromAcceptedFormat(
ws,
exchangeBaseUrl,
getExchangeRequestTimeout(),
acceptedFormat,
);
if (tosDownload.tosContentType === contentType) { if (tosDownload.tosContentType === contentType) {
return { return {
@ -474,7 +523,7 @@ async function getExchangeTos(
contentType, contentType,
}; };
} }
await updateExchangeTermsOfService(ws, exchangeBaseUrl, tosDownload) await updateExchangeTermsOfService(ws, exchangeBaseUrl, tosDownload);
return { return {
acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag, acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag,
@ -482,7 +531,6 @@ async function getExchangeTos(
content: tosDownload.tosText, content: tosDownload.tosText,
contentType: tosDownload.tosContentType, contentType: tosDownload.tosContentType,
}; };
} }
async function listKnownBankAccounts( async function listKnownBankAccounts(
@ -641,9 +689,15 @@ async function dumpCoins(ws: InternalWalletState): Promise<CoinDumpJson> {
} }
withdrawalReservePub = ws.reservePub; withdrawalReservePub = ws.reservePub;
} }
const denomInfo = await ws.getDenomInfo(
ws,
tx,
c.exchangeBaseUrl,
c.denomPubHash,
);
coinsJson.coins.push({ coinsJson.coins.push({
coin_pub: c.coinPub, coin_pub: c.coinPub,
denom_pub: c.denomPub, denom_pub: denomInfo?.denomPub!,
denom_pub_hash: c.denomPubHash, denom_pub_hash: c.denomPubHash,
denom_value: Amounts.stringify(denom.value), denom_value: Amounts.stringify(denom.value),
exchange_base_url: c.exchangeBaseUrl, exchange_base_url: c.exchangeBaseUrl,
@ -1030,7 +1084,7 @@ export async function handleCoreApiRequest(
try { try {
logger.error("Caught unexpected exception:"); logger.error("Caught unexpected exception:");
logger.error(e.stack); logger.error(e.stack);
} catch (e) { } } catch (e) {}
return { return {
type: "error", type: "error",
operation, operation,
@ -1236,7 +1290,10 @@ class InternalWalletStateImpl implements InternalWalletState {
* Run an async function after acquiring a list of locks, identified * Run an async function after acquiring a list of locks, identified
* by string tokens. * by string tokens.
*/ */
async runSequentialized<T>(tokens: string[], f: () => Promise<T>): Promise<T> { async runSequentialized<T>(
tokens: string[],
f: () => Promise<T>,
): Promise<T> {
// Make sure locks are always acquired in the same order // Make sure locks are always acquired in the same order
tokens = [...tokens].sort(); tokens = [...tokens].sort();
@ -1269,4 +1326,3 @@ class InternalWalletStateImpl implements InternalWalletState {
} }
} }
} }