diff options
Diffstat (limited to 'packages/taler-wallet-core/src/operations/exchanges.ts')
-rw-r--r-- | packages/taler-wallet-core/src/operations/exchanges.ts | 188 |
1 files changed, 162 insertions, 26 deletions
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index 67404665c..b4d45db2c 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -25,6 +25,7 @@ */ import { AbsoluteTime, + AgeRestriction, Amounts, CancellationToken, DeleteExchangeRequest, @@ -50,7 +51,10 @@ import { LibtoolVersion, Logger, NotificationType, + OperationErrorInfo, Recoup, + ScopeInfo, + ScopeType, TalerError, TalerErrorCode, TalerErrorDetail, @@ -88,7 +92,7 @@ import { ExchangeEntryDbRecordStatus, ExchangeEntryDbUpdateStatus, PendingTaskType, - WalletDbReadWriteTransaction, + WalletDbReadOnlyTransactionArr, WalletDbReadWriteTransactionArr, createTimeline, isWithdrawableDenom, @@ -103,7 +107,6 @@ import { import { InternalWalletState } from "../internal-wallet-state.js"; import { checkDbInvariant } from "../util/invariants.js"; import { - DbReadOnlyTransaction, DbReadOnlyTransactionArr, GetReadOnlyAccess, GetReadWriteAccess, @@ -114,9 +117,10 @@ import { TaskRunResult, TaskRunResultType, constructTaskIdentifier, + getExchangeEntryStatusFromRecord, getExchangeState, getExchangeTosStatusFromRecord, - makeExchangeListItem, + getExchangeUpdateStatusFromRecord, runTaskWithErrorReporting, } from "./common.js"; @@ -206,6 +210,105 @@ async function getExchangeRecordsInternal( ]); } +export async function getExchangeScopeInfo( + tx: WalletDbReadOnlyTransactionArr< + [ + "exchanges", + "exchangeDetails", + "globalCurrencyExchanges", + "globalCurrencyAuditors", + ] + >, + exchangeBaseUrl: string, + currency: string, +): Promise<ScopeInfo> { + const det = await getExchangeRecordsInternal(tx, exchangeBaseUrl); + if (!det) { + return { + type: ScopeType.Exchange, + currency: currency, + url: exchangeBaseUrl, + }; + } + return internalGetExchangeScopeInfo(tx, det); +} + +async function internalGetExchangeScopeInfo( + tx: WalletDbReadOnlyTransactionArr< + ["globalCurrencyExchanges", "globalCurrencyAuditors"] + >, + exchangeDetails: ExchangeDetailsRecord, +): Promise<ScopeInfo> { + const globalExchangeRec = + await tx.globalCurrencyExchanges.indexes.byCurrencyAndUrlAndPub.get([ + exchangeDetails.currency, + exchangeDetails.exchangeBaseUrl, + exchangeDetails.masterPublicKey, + ]); + if (globalExchangeRec) { + return { + currency: exchangeDetails.currency, + type: ScopeType.Global, + }; + } else { + for (const aud of exchangeDetails.auditors) { + const globalAuditorRec = + await tx.globalCurrencyAuditors.indexes.byCurrencyAndUrlAndPub.get([ + exchangeDetails.currency, + aud.auditor_url, + aud.auditor_pub, + ]); + if (globalAuditorRec) { + return { + currency: exchangeDetails.currency, + type: ScopeType.Auditor, + url: aud.auditor_url, + }; + } + } + } + return { + currency: exchangeDetails.currency, + type: ScopeType.Exchange, + url: exchangeDetails.exchangeBaseUrl, + }; +} + +async function makeExchangeListItem( + tx: WalletDbReadOnlyTransactionArr< + ["globalCurrencyExchanges", "globalCurrencyAuditors"] + >, + r: ExchangeEntryRecord, + exchangeDetails: ExchangeDetailsRecord | undefined, + lastError: TalerErrorDetail | undefined, +): Promise<ExchangeListItem> { + const lastUpdateErrorInfo: OperationErrorInfo | undefined = lastError + ? { + error: lastError, + } + : undefined; + + let scopeInfo: ScopeInfo | undefined = undefined; + + if (exchangeDetails) { + scopeInfo = await internalGetExchangeScopeInfo(tx, exchangeDetails); + } + + return { + exchangeBaseUrl: r.baseUrl, + currency: exchangeDetails?.currency ?? r.presetCurrencyHint, + exchangeUpdateStatus: getExchangeUpdateStatusFromRecord(r), + exchangeEntryStatus: getExchangeEntryStatusFromRecord(r), + tosStatus: getExchangeTosStatusFromRecord(r), + ageRestrictionOptions: exchangeDetails?.ageMask + ? AgeRestriction.getAgeGroupsFromMask(exchangeDetails.ageMask) + : [], + paytoUris: exchangeDetails?.wireInfo.accounts.map((x) => x.payto_uri) ?? [], + lastUpdateErrorInfo, + scopeInfo, + }; +} + export interface ExchangeWireDetails { currency: string; masterPublicKey: EddsaPublicKeyString; @@ -240,14 +343,15 @@ export async function lookupExchangeByUri( ws: InternalWalletState, req: GetExchangeEntryByUrlRequest, ): Promise<ExchangeListItem> { - return await ws.db - .mktx((x) => [ - x.exchanges, - x.exchangeDetails, - x.denominations, - x.operationRetries, - ]) - .runReadOnly(async (tx) => { + return await ws.db.runReadOnlyTx( + [ + "exchanges", + "exchangeDetails", + "operationRetries", + "globalCurrencyAuditors", + "globalCurrencyExchanges", + ], + async (tx) => { const exchangeRec = await tx.exchanges.get(req.exchangeBaseUrl); if (!exchangeRec) { throw Error("exchange not found"); @@ -259,12 +363,14 @@ export async function lookupExchangeByUri( const opRetryRecord = await tx.operationRetries.get( TaskIdentifiers.forExchangeUpdate(exchangeRec), ); - return makeExchangeListItem( + return await makeExchangeListItem( + tx, exchangeRec, exchangeDetails, opRetryRecord?.lastError, ); - }); + }, + ); } /** @@ -800,6 +906,7 @@ export interface ReadyExchangeSummary { wireInfo: WireInfo; protocolVersionRange: string; tosAcceptedTimestamp: TalerPreciseTimestamp | undefined; + scopeInfo: ScopeInfo; } /** @@ -863,14 +970,26 @@ export async function fetchFreshExchange( ); } - const { exchange, exchangeDetails, retryInfo } = await ws.db - .mktx((x) => [x.exchanges, x.exchangeDetails, x.operationRetries]) - .runReadOnly(async (tx) => { - const exchange = await tx.exchanges.get(canonUrl); - const exchangeDetails = await getExchangeRecordsInternal(tx, canonUrl); - const retryInfo = await tx.operationRetries.get(operationId); - return { exchange, exchangeDetails, retryInfo }; - }); + const { exchange, exchangeDetails, retryInfo, scopeInfo } = + await ws.db.runReadOnlyTx( + [ + "exchanges", + "exchangeDetails", + "operationRetries", + "globalCurrencyAuditors", + "globalCurrencyExchanges", + ], + async (tx) => { + const exchange = await tx.exchanges.get(canonUrl); + const exchangeDetails = await getExchangeRecordsInternal(tx, canonUrl); + const retryInfo = await tx.operationRetries.get(operationId); + let scopeInfo: ScopeInfo | undefined = undefined; + if (exchange && exchangeDetails) { + scopeInfo = await internalGetExchangeScopeInfo(tx, exchangeDetails); + } + return { exchange, exchangeDetails, retryInfo, scopeInfo }; + }, + ); if (!exchange) { throw Error("exchange entry does not exist anymore"); @@ -891,6 +1010,10 @@ export async function fetchFreshExchange( throw Error("invariant failed"); } + if (!scopeInfo) { + throw Error("invariant failed"); + } + const res: ReadyExchangeSummary = { currency: exchangeDetails.currency, exchangeBaseUrl: canonUrl, @@ -903,6 +1026,7 @@ export async function fetchFreshExchange( tosAcceptedTimestamp: timestampOptionalPreciseFromDb( exchange.tosAcceptedTimestamp, ), + scopeInfo, }; if (options.expectedMasterPub) { @@ -1309,9 +1433,15 @@ export async function listExchanges( ws: InternalWalletState, ): Promise<ExchangesListResponse> { const exchanges: ExchangeListItem[] = []; - await ws.db - .mktx((x) => [x.exchanges, x.exchangeDetails, x.operationRetries]) - .runReadOnly(async (tx) => { + await ws.db.runReadOnlyTx( + [ + "exchanges", + "operationRetries", + "exchangeDetails", + "globalCurrencyAuditors", + "globalCurrencyExchanges", + ], + async (tx) => { const exchangeRecords = await tx.exchanges.iter().toArray(); for (const r of exchangeRecords) { const taskId = constructTaskIdentifier({ @@ -1321,10 +1451,16 @@ export async function listExchanges( const exchangeDetails = await getExchangeRecordsInternal(tx, r.baseUrl); const opRetryRecord = await tx.operationRetries.get(taskId); exchanges.push( - makeExchangeListItem(r, exchangeDetails, opRetryRecord?.lastError), + await makeExchangeListItem( + tx, + r, + exchangeDetails, + opRetryRecord?.lastError, + ), ); } - }); + }, + ); return { exchanges }; } |