wallet-core: towards DD48

This commit is contained in:
Florian Dold 2023-08-30 15:54:56 +02:00
parent 88f7338d7c
commit d19aef746c
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
10 changed files with 182 additions and 93 deletions

View File

@ -312,6 +312,14 @@ export namespace Duration {
} }
export namespace AbsoluteTime { export namespace AbsoluteTime {
export function getStampMsNow(): number {
return new Date().getTime();
}
export function getStampMsNever(): number {
return Number.MAX_SAFE_INTEGER;
}
export function now(): AbsoluteTime { export function now(): AbsoluteTime {
return { return {
t_ms: new Date().getTime() + timeshift, t_ms: new Date().getTime() + timeshift,
@ -398,6 +406,13 @@ export namespace AbsoluteTime {
}; };
} }
export function fromStampMs(stampMs: number): AbsoluteTime {
return {
t_ms: stampMs,
[opaque_AbsoluteTime]: true,
};
}
export function fromPreciseTimestamp(t: TalerPreciseTimestamp): AbsoluteTime { export function fromPreciseTimestamp(t: TalerPreciseTimestamp): AbsoluteTime {
if (t.t_s === "never") { if (t.t_s === "never") {
return { t_ms: "never", [opaque_AbsoluteTime]: true }; return { t_ms: "never", [opaque_AbsoluteTime]: true };
@ -409,6 +424,13 @@ export namespace AbsoluteTime {
}; };
} }
export function toStampMs(at: AbsoluteTime): number {
if (at.t_ms === "never") {
return Number.MAX_SAFE_INTEGER;
}
return at.t_ms;
}
export function toPreciseTimestamp(at: AbsoluteTime): TalerPreciseTimestamp { export function toPreciseTimestamp(at: AbsoluteTime): TalerPreciseTimestamp {
if (at.t_ms == "never") { if (at.t_ms == "never") {
return { return {

View File

@ -1262,17 +1262,25 @@ export interface ExchangeFullDetails {
} }
export enum ExchangeTosStatus { export enum ExchangeTosStatus {
New = "new", Pending = "pending",
Proposed = "proposed",
Accepted = "accepted", Accepted = "accepted",
Changed = "changed",
NotFound = "not-found",
Unknown = "unknown",
} }
export enum ExchangeEntryStatus { export enum ExchangeEntryStatus {
Unknown = "unknown", Preset = "preset",
Outdated = "outdated", Ephemeral = "ephemeral",
Ok = "ok", Used = "used",
}
export enum ExchangeUpdateStatus {
Initial = "initial",
InitialUpdate = "initial(update)",
Suspended = "suspended",
Failed = "failed",
OutdatedUpdate = "outdated(update)",
Ready = "ready",
ReadyUpdate = "ready(update)",
} }
export interface OperationErrorInfo { export interface OperationErrorInfo {
@ -1285,13 +1293,9 @@ export interface ExchangeListItem {
currency: string | undefined; currency: string | undefined;
paytoUris: string[]; paytoUris: string[];
tosStatus: ExchangeTosStatus; tosStatus: ExchangeTosStatus;
exchangeStatus: ExchangeEntryStatus; exchangeEntryStatus: ExchangeEntryStatus;
exchangeUpdateStatus: ExchangeUpdateStatus;
ageRestrictionOptions: number[]; ageRestrictionOptions: number[];
/**
* Permanently added to the wallet, as opposed to just
* temporarily queried.
*/
permanent: boolean;
/** /**
* Information about the last error that occurred when trying * Information about the last error that occurred when trying
@ -1370,8 +1374,8 @@ export const codecForExchangeListItem = (): Codec<ExchangeListItem> =>
.property("exchangeBaseUrl", codecForString()) .property("exchangeBaseUrl", codecForString())
.property("paytoUris", codecForList(codecForString())) .property("paytoUris", codecForList(codecForString()))
.property("tosStatus", codecForAny()) .property("tosStatus", codecForAny())
.property("exchangeStatus", codecForAny()) .property("exchangeEntryStatus", codecForAny())
.property("permanent", codecForBoolean()) .property("exchangeUpdateStatus", codecForAny())
.property("ageRestrictionOptions", codecForList(codecForNumber())) .property("ageRestrictionOptions", codecForList(codecForNumber()))
.build("ExchangeListItem"); .build("ExchangeListItem");

View File

@ -42,7 +42,7 @@ import { HttpRequestLibrary } from "@gnu-taler/taler-util/http";
import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js"; import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
import { import {
ExchangeDetailsRecord, ExchangeDetailsRecord,
ExchangeRecord, ExchangeEntryRecord,
RefreshReasonDetails, RefreshReasonDetails,
WalletStoresV1, WalletStoresV1,
} from "./db.js"; } from "./db.js";
@ -108,7 +108,7 @@ export interface ExchangeOperations {
): Promise<ExchangeDetailsRecord | undefined>; ): Promise<ExchangeDetailsRecord | undefined>;
getExchangeTrust( getExchangeTrust(
ws: InternalWalletState, ws: InternalWalletState,
exchangeInfo: ExchangeRecord, exchangeInfo: ExchangeEntryRecord,
): Promise<TrustInfo>; ): Promise<TrustInfo>;
updateExchangeFromUrl( updateExchangeFromUrl(
ws: InternalWalletState, ws: InternalWalletState,
@ -118,7 +118,7 @@ export interface ExchangeOperations {
cancellationToken?: CancellationToken; cancellationToken?: CancellationToken;
}, },
): Promise<{ ): Promise<{
exchange: ExchangeRecord; exchange: ExchangeEntryRecord;
exchangeDetails: ExchangeDetailsRecord; exchangeDetails: ExchangeDetailsRecord;
}>; }>;
} }

View File

@ -342,20 +342,19 @@ export async function importBackup(
if (existingExchange) { if (existingExchange) {
continue; continue;
} }
await tx.exchanges.put({ // await tx.exchanges.put({
baseUrl: backupExchange.base_url, // baseUrl: backupExchange.base_url,
detailsPointer: { // detailsPointer: {
currency: backupExchange.currency, // currency: backupExchange.currency,
masterPublicKey: backupExchange.master_public_key, // masterPublicKey: backupExchange.master_public_key,
updateClock: backupExchange.update_clock, // updateClock: backupExchange.update_clock,
}, // },
permanent: true, // lastUpdate: undefined,
lastUpdate: undefined, // nextUpdate: TalerPreciseTimestamp.now(),
nextUpdate: TalerPreciseTimestamp.now(), // nextRefreshCheck: TalerPreciseTimestamp.now(),
nextRefreshCheck: TalerPreciseTimestamp.now(), // lastKeysEtag: undefined,
lastKeysEtag: undefined, // lastWireEtag: undefined,
lastWireEtag: undefined, // });
});
} }
for (const backupExchangeDetails of backupBlob.exchange_details) { for (const backupExchangeDetails of backupBlob.exchange_details) {

View File

@ -30,6 +30,7 @@ import {
ExchangeEntryStatus, ExchangeEntryStatus,
ExchangeListItem, ExchangeListItem,
ExchangeTosStatus, ExchangeTosStatus,
ExchangeUpdateStatus,
getErrorDetailFromException, getErrorDetailFromException,
j2s, j2s,
Logger, Logger,
@ -47,7 +48,7 @@ import {
WalletStoresV1, WalletStoresV1,
CoinRecord, CoinRecord,
ExchangeDetailsRecord, ExchangeDetailsRecord,
ExchangeRecord, ExchangeEntryRecord,
BackupProviderRecord, BackupProviderRecord,
DepositGroupRecord, DepositGroupRecord,
PeerPullPaymentIncomingRecord, PeerPullPaymentIncomingRecord,
@ -59,6 +60,8 @@ import {
RefreshGroupRecord, RefreshGroupRecord,
RewardRecord, RewardRecord,
WithdrawalGroupRecord, WithdrawalGroupRecord,
ExchangeEntryDbUpdateStatus,
ExchangeEntryDbRecordStatus,
} from "../db.js"; } from "../db.js";
import { makeErrorDetail, TalerError } from "@gnu-taler/taler-util"; import { makeErrorDetail, TalerError } from "@gnu-taler/taler-util";
import { InternalWalletState } from "../internal-wallet-state.js"; import { InternalWalletState } from "../internal-wallet-state.js";
@ -529,16 +532,16 @@ export function getExchangeTosStatus(
exchangeDetails: ExchangeDetailsRecord, exchangeDetails: ExchangeDetailsRecord,
): ExchangeTosStatus { ): ExchangeTosStatus {
if (!exchangeDetails.tosAccepted) { if (!exchangeDetails.tosAccepted) {
return ExchangeTosStatus.New; return ExchangeTosStatus.Proposed;
} }
if (exchangeDetails.tosAccepted?.etag == exchangeDetails.tosCurrentEtag) { if (exchangeDetails.tosAccepted?.etag == exchangeDetails.tosCurrentEtag) {
return ExchangeTosStatus.Accepted; return ExchangeTosStatus.Accepted;
} }
return ExchangeTosStatus.Changed; return ExchangeTosStatus.Proposed;
} }
export function makeExchangeListItem( export function makeExchangeListItem(
r: ExchangeRecord, r: ExchangeEntryRecord,
exchangeDetails: ExchangeDetailsRecord | undefined, exchangeDetails: ExchangeDetailsRecord | undefined,
lastError: TalerErrorDetail | undefined, lastError: TalerErrorDetail | undefined,
): ExchangeListItem { ): ExchangeListItem {
@ -547,30 +550,57 @@ export function makeExchangeListItem(
error: lastError, error: lastError,
} }
: undefined; : undefined;
if (!exchangeDetails) {
return { let exchangeUpdateStatus: ExchangeUpdateStatus;
exchangeBaseUrl: r.baseUrl, switch (r.updateStatus) {
currency: undefined, case ExchangeEntryDbUpdateStatus.Failed:
tosStatus: ExchangeTosStatus.Unknown, exchangeUpdateStatus = ExchangeUpdateStatus.Failed;
paytoUris: [], break;
exchangeStatus: ExchangeEntryStatus.Unknown, case ExchangeEntryDbUpdateStatus.Initial:
permanent: r.permanent, exchangeUpdateStatus = ExchangeUpdateStatus.Initial;
ageRestrictionOptions: [], break;
lastUpdateErrorInfo, case ExchangeEntryDbUpdateStatus.InitialUpdate:
}; exchangeUpdateStatus = ExchangeUpdateStatus.InitialUpdate;
break;
case ExchangeEntryDbUpdateStatus.OutdatedUpdate:
exchangeUpdateStatus = ExchangeUpdateStatus.OutdatedUpdate;
break;
case ExchangeEntryDbUpdateStatus.Ready:
exchangeUpdateStatus = ExchangeUpdateStatus.Ready;
break;
case ExchangeEntryDbUpdateStatus.ReadyUpdate:
exchangeUpdateStatus = ExchangeUpdateStatus.ReadyUpdate;
break;
case ExchangeEntryDbUpdateStatus.Suspended:
exchangeUpdateStatus = ExchangeUpdateStatus.Suspended;
break;
} }
let exchangeStatus;
exchangeStatus = ExchangeEntryStatus.Ok; let exchangeEntryStatus: ExchangeEntryStatus;
switch (r.entryStatus) {
case ExchangeEntryDbRecordStatus.Ephemeral:
exchangeEntryStatus = ExchangeEntryStatus.Ephemeral;
break;
case ExchangeEntryDbRecordStatus.Preset:
exchangeEntryStatus = ExchangeEntryStatus.Preset;
break;
case ExchangeEntryDbRecordStatus.Used:
exchangeEntryStatus = ExchangeEntryStatus.Used;
break;
}
return { return {
exchangeBaseUrl: r.baseUrl, exchangeBaseUrl: r.baseUrl,
currency: exchangeDetails.currency, currency: exchangeDetails?.currency,
tosStatus: getExchangeTosStatus(exchangeDetails), exchangeUpdateStatus,
paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri), exchangeEntryStatus,
exchangeStatus, tosStatus: exchangeDetails
permanent: r.permanent, ? getExchangeTosStatus(exchangeDetails)
ageRestrictionOptions: exchangeDetails.ageMask : ExchangeTosStatus.Pending,
ageRestrictionOptions: exchangeDetails?.ageMask
? AgeRestriction.getAgeGroupsFromMask(exchangeDetails.ageMask) ? AgeRestriction.getAgeGroupsFromMask(exchangeDetails.ageMask)
: [], : [],
paytoUris: exchangeDetails?.wireInfo.accounts.map((x) => x.payto_uri) ?? [],
lastUpdateErrorInfo, lastUpdateErrorInfo,
}; };
} }
@ -892,13 +922,13 @@ export namespace TaskIdentifiers {
export function forWithdrawal(wg: WithdrawalGroupRecord): TaskId { export function forWithdrawal(wg: WithdrawalGroupRecord): TaskId {
return `${PendingTaskType.Withdraw}:${wg.withdrawalGroupId}` as TaskId; return `${PendingTaskType.Withdraw}:${wg.withdrawalGroupId}` as TaskId;
} }
export function forExchangeUpdate(exch: ExchangeRecord): TaskId { export function forExchangeUpdate(exch: ExchangeEntryRecord): TaskId {
return `${PendingTaskType.ExchangeUpdate}:${exch.baseUrl}` as TaskId; return `${PendingTaskType.ExchangeUpdate}:${exch.baseUrl}` as TaskId;
} }
export function forExchangeUpdateFromUrl(exchBaseUrl: string): TaskId { export function forExchangeUpdateFromUrl(exchBaseUrl: string): TaskId {
return `${PendingTaskType.ExchangeUpdate}:${exchBaseUrl}` as TaskId; return `${PendingTaskType.ExchangeUpdate}:${exchBaseUrl}` as TaskId;
} }
export function forExchangeCheckRefresh(exch: ExchangeRecord): TaskId { export function forExchangeCheckRefresh(exch: ExchangeEntryRecord): TaskId {
return `${PendingTaskType.ExchangeCheckRefresh}:${exch.baseUrl}` as TaskId; return `${PendingTaskType.ExchangeCheckRefresh}:${exch.baseUrl}` as TaskId;
} }
export function forTipPickup(tipRecord: RewardRecord): TaskId { export function forTipPickup(tipRecord: RewardRecord): TaskId {

View File

@ -32,6 +32,7 @@ import {
encodeCrock, encodeCrock,
ExchangeAuditor, ExchangeAuditor,
ExchangeDenomination, ExchangeDenomination,
ExchangeEntryStatus,
ExchangeGlobalFees, ExchangeGlobalFees,
ExchangeSignKeyJson, ExchangeSignKeyJson,
ExchangeWireJson, ExchangeWireJson,
@ -66,10 +67,15 @@ import {
DenominationRecord, DenominationRecord,
DenominationVerificationStatus, DenominationVerificationStatus,
ExchangeDetailsRecord, ExchangeDetailsRecord,
ExchangeRecord, ExchangeEntryRecord,
WalletStoresV1, WalletStoresV1,
} from "../db.js"; } from "../db.js";
import { isWithdrawableDenom } from "../index.js"; import {
ExchangeEntryDbRecordStatus,
ExchangeEntryDbUpdateStatus,
isWithdrawableDenom,
WalletDbReadWriteTransaction,
} from "../index.js";
import { InternalWalletState, TrustInfo } from "../internal-wallet-state.js"; import { InternalWalletState, TrustInfo } from "../internal-wallet-state.js";
import { checkDbInvariant } from "../util/invariants.js"; import { checkDbInvariant } from "../util/invariants.js";
import { import {
@ -326,6 +332,26 @@ export async function downloadExchangeInfo(
}; };
} }
export async function addPresetExchangeEntry(
tx: WalletDbReadWriteTransaction<"exchanges">,
exchangeBaseUrl: string,
): Promise<void> {
let exchange = await tx.exchanges.get(exchangeBaseUrl);
if (!exchange) {
const r: ExchangeEntryRecord = {
entryStatus: ExchangeEntryDbRecordStatus.Preset,
updateStatus: ExchangeEntryDbUpdateStatus.Initial,
baseUrl: exchangeBaseUrl,
detailsPointer: undefined,
lastUpdate: undefined,
lastKeysEtag: undefined,
nextRefreshCheckStampMs: AbsoluteTime.getStampMsNever(),
nextUpdateStampMs: AbsoluteTime.getStampMsNever(),
};
await tx.exchanges.put(r);
}
}
export async function provideExchangeRecordInTx( export async function provideExchangeRecordInTx(
ws: InternalWalletState, ws: InternalWalletState,
tx: GetReadWriteAccess<{ tx: GetReadWriteAccess<{
@ -335,20 +361,20 @@ export async function provideExchangeRecordInTx(
baseUrl: string, baseUrl: string,
now: AbsoluteTime, now: AbsoluteTime,
): Promise<{ ): Promise<{
exchange: ExchangeRecord; exchange: ExchangeEntryRecord;
exchangeDetails: ExchangeDetailsRecord | undefined; exchangeDetails: ExchangeDetailsRecord | undefined;
}> { }> {
let exchange = await tx.exchanges.get(baseUrl); let exchange = await tx.exchanges.get(baseUrl);
if (!exchange) { if (!exchange) {
const r: ExchangeRecord = { const r: ExchangeEntryRecord = {
permanent: true, entryStatus: ExchangeEntryDbRecordStatus.Ephemeral,
updateStatus: ExchangeEntryDbUpdateStatus.InitialUpdate,
baseUrl: baseUrl, baseUrl: baseUrl,
detailsPointer: undefined, detailsPointer: undefined,
lastUpdate: undefined, lastUpdate: undefined,
nextUpdate: AbsoluteTime.toPreciseTimestamp(now), nextUpdateStampMs: AbsoluteTime.getStampMsNever(),
nextRefreshCheck: AbsoluteTime.toPreciseTimestamp(now), nextRefreshCheckStampMs: AbsoluteTime.getStampMsNever(),
lastKeysEtag: undefined, lastKeysEtag: undefined,
lastWireEtag: undefined,
}; };
await tx.exchanges.put(r); await tx.exchanges.put(r);
exchange = r; exchange = r;
@ -534,6 +560,10 @@ export async function downloadTosFromAcceptedFormat(
); );
} }
/**
* FIXME: Split this into two parts: (a) triggering the exchange
* to be updated and (b) waiting for the update to finish.
*/
export async function updateExchangeFromUrl( export async function updateExchangeFromUrl(
ws: InternalWalletState, ws: InternalWalletState,
baseUrl: string, baseUrl: string,
@ -543,7 +573,7 @@ export async function updateExchangeFromUrl(
cancellationToken?: CancellationToken; cancellationToken?: CancellationToken;
} = {}, } = {},
): Promise<{ ): Promise<{
exchange: ExchangeRecord; exchange: ExchangeEntryRecord;
exchangeDetails: ExchangeDetailsRecord; exchangeDetails: ExchangeDetailsRecord;
}> { }> {
const canonUrl = canonicalizeBaseUrl(baseUrl); const canonUrl = canonicalizeBaseUrl(baseUrl);
@ -613,7 +643,7 @@ export async function updateExchangeFromUrlHandler(
!forceNow && !forceNow &&
exchangeDetails !== undefined && exchangeDetails !== undefined &&
!AbsoluteTime.isExpired( !AbsoluteTime.isExpired(
AbsoluteTime.fromPreciseTimestamp(exchange.nextUpdate), AbsoluteTime.fromStampMs(exchange.nextUpdateStampMs),
) )
) { ) {
logger.trace("using existing exchange info"); logger.trace("using existing exchange info");
@ -755,11 +785,11 @@ export async function updateExchangeFromUrlHandler(
newDetails.rowId = existingDetails.rowId; newDetails.rowId = existingDetails.rowId;
} }
r.lastUpdate = TalerPreciseTimestamp.now(); r.lastUpdate = TalerPreciseTimestamp.now();
r.nextUpdate = AbsoluteTime.toPreciseTimestamp( r.nextUpdateStampMs = AbsoluteTime.toStampMs(
AbsoluteTime.fromProtocolTimestamp(keysInfo.expiry), AbsoluteTime.fromProtocolTimestamp(keysInfo.expiry),
); );
// New denominations might be available. // New denominations might be available.
r.nextRefreshCheck = TalerPreciseTimestamp.now(); r.nextRefreshCheckStampMs = AbsoluteTime.getStampMsNow();
if (detailsPointerChanged) { if (detailsPointerChanged) {
r.detailsPointer = { r.detailsPointer = {
currency: newDetails.currency, currency: newDetails.currency,
@ -948,7 +978,7 @@ export async function getExchangePaytoUri(
*/ */
export async function getExchangeTrust( export async function getExchangeTrust(
ws: InternalWalletState, ws: InternalWalletState,
exchangeInfo: ExchangeRecord, exchangeInfo: ExchangeEntryRecord,
): Promise<TrustInfo> { ): Promise<TrustInfo> {
let isTrusted = false; let isTrusted = false;
let isAudited = false; let isAudited = false;

View File

@ -45,6 +45,7 @@ import {
PeerPushPaymentIncomingRecord, PeerPushPaymentIncomingRecord,
RefundGroupRecord, RefundGroupRecord,
RefundGroupStatus, RefundGroupStatus,
ExchangeEntryDbUpdateStatus,
} from "../db.js"; } from "../db.js";
import { import {
PendingOperationsResponse, PendingOperationsResponse,
@ -81,19 +82,25 @@ async function gatherExchangePending(
ws: InternalWalletState, ws: InternalWalletState,
tx: GetReadOnlyAccess<{ tx: GetReadOnlyAccess<{
exchanges: typeof WalletStoresV1.exchanges; exchanges: typeof WalletStoresV1.exchanges;
exchangeDetails: typeof WalletStoresV1.exchangeDetails;
operationRetries: typeof WalletStoresV1.operationRetries; operationRetries: typeof WalletStoresV1.operationRetries;
}>, }>,
now: AbsoluteTime, now: AbsoluteTime,
resp: PendingOperationsResponse, resp: PendingOperationsResponse,
): Promise<void> { ): Promise<void> {
// FIXME: We should do a range query here based on the update time. // FIXME: We should do a range query here based on the update time
// and/or the entry state.
await tx.exchanges.iter().forEachAsync(async (exch) => { await tx.exchanges.iter().forEachAsync(async (exch) => {
switch (exch.updateStatus) {
case ExchangeEntryDbUpdateStatus.Initial:
case ExchangeEntryDbUpdateStatus.Suspended:
case ExchangeEntryDbUpdateStatus.Failed:
return;
}
const opTag = TaskIdentifiers.forExchangeUpdate(exch); const opTag = TaskIdentifiers.forExchangeUpdate(exch);
let opr = await tx.operationRetries.get(opTag); let opr = await tx.operationRetries.get(opTag);
const timestampDue = const timestampDue =
opr?.retryInfo.nextRetry ?? opr?.retryInfo.nextRetry ??
AbsoluteTime.fromPreciseTimestamp(exch.nextUpdate); AbsoluteTime.fromStampMs(exch.nextUpdateStampMs);
resp.pendingOperations.push({ resp.pendingOperations.push({
type: PendingTaskType.ExchangeUpdate, type: PendingTaskType.ExchangeUpdate,
...getPendingCommon(ws, opTag, timestampDue), ...getPendingCommon(ws, opTag, timestampDue),
@ -108,7 +115,7 @@ async function gatherExchangePending(
resp.pendingOperations.push({ resp.pendingOperations.push({
type: PendingTaskType.ExchangeCheckRefresh, type: PendingTaskType.ExchangeCheckRefresh,
...getPendingCommon(ws, opTag, timestampDue), ...getPendingCommon(ws, opTag, timestampDue),
timestampDue: AbsoluteTime.fromPreciseTimestamp(exch.nextRefreshCheck), timestampDue: AbsoluteTime.fromStampMs(exch.nextRefreshCheckStampMs),
givesLifeness: false, givesLifeness: false,
exchangeBaseUrl: exch.baseUrl, exchangeBaseUrl: exch.baseUrl,
}); });
@ -184,8 +191,9 @@ export async function iterRecordsForWithdrawal(
WithdrawalGroupStatus.PendingRegisteringBank, WithdrawalGroupStatus.PendingRegisteringBank,
WithdrawalGroupStatus.PendingAml, WithdrawalGroupStatus.PendingAml,
); );
withdrawalGroupRecords = withdrawalGroupRecords = await tx.withdrawalGroups.indexes.byStatus.getAll(
await tx.withdrawalGroups.indexes.byStatus.getAll(range); range,
);
} else { } else {
withdrawalGroupRecords = withdrawalGroupRecords =
await tx.withdrawalGroups.indexes.byStatus.getAll(); await tx.withdrawalGroups.indexes.byStatus.getAll();
@ -344,12 +352,8 @@ export async function iterRecordsForRefund(
f: (r: RefundGroupRecord) => Promise<void>, f: (r: RefundGroupRecord) => Promise<void>,
): Promise<void> { ): Promise<void> {
if (filter.onlyState === "nonfinal") { if (filter.onlyState === "nonfinal") {
const keyRange = GlobalIDB.KeyRange.only( const keyRange = GlobalIDB.KeyRange.only(RefundGroupStatus.Pending);
RefundGroupStatus.Pending await tx.refundGroups.indexes.byStatus.iter(keyRange).forEachAsync(f);
);
await tx.refundGroups.indexes.byStatus
.iter(keyRange)
.forEachAsync(f);
} else { } else {
await tx.refundGroups.iter().forEachAsync(f); await tx.refundGroups.iter().forEachAsync(f);
} }

View File

@ -1196,8 +1196,8 @@ export async function autoRefresh(
logger.trace( logger.trace(
`next refresh check at ${AbsoluteTime.toIsoString(minCheckThreshold)}`, `next refresh check at ${AbsoluteTime.toIsoString(minCheckThreshold)}`,
); );
exchange.nextRefreshCheck = exchange.nextRefreshCheckStampMs =
AbsoluteTime.toPreciseTimestamp(minCheckThreshold); AbsoluteTime.toStampMs(minCheckThreshold);
await tx.exchanges.put(exchange); await tx.exchanges.put(exchange);
}); });
return TaskRunResult.finished(); return TaskRunResult.finished();

View File

@ -128,6 +128,8 @@ import {
} from "../util/coinSelection.js"; } from "../util/coinSelection.js";
import { import {
ExchangeDetailsRecord, ExchangeDetailsRecord,
ExchangeEntryDbRecordStatus,
ExchangeEntryDbUpdateStatus,
PendingTaskType, PendingTaskType,
isWithdrawableDenom, isWithdrawableDenom,
} from "../index.js"; } from "../index.js";
@ -2341,10 +2343,6 @@ export async function internalPerformCreateWithdrawalGroup(
}>, }>,
prep: PrepareCreateWithdrawalGroupResult, prep: PrepareCreateWithdrawalGroupResult,
): Promise<PerformCreateWithdrawalGroupResult> { ): Promise<PerformCreateWithdrawalGroupResult> {
const transactionId = constructTransactionIdentifier({
tag: TransactionType.Withdrawal,
withdrawalGroupId: prep.withdrawalGroup.withdrawalGroupId,
});
const { withdrawalGroup } = prep; const { withdrawalGroup } = prep;
if (!prep.creationInfo) { if (!prep.creationInfo) {
return { withdrawalGroup, transitionInfo: undefined }; return { withdrawalGroup, transitionInfo: undefined };
@ -2361,6 +2359,7 @@ export async function internalPerformCreateWithdrawalGroup(
const exchange = await tx.exchanges.get(withdrawalGroup.exchangeBaseUrl); const exchange = await tx.exchanges.get(withdrawalGroup.exchangeBaseUrl);
if (exchange) { if (exchange) {
exchange.lastWithdrawal = TalerPreciseTimestamp.now(); exchange.lastWithdrawal = TalerPreciseTimestamp.now();
exchange.entryStatus = ExchangeEntryDbRecordStatus.Used;
await tx.exchanges.put(exchange); await tx.exchanges.put(exchange);
} }

View File

@ -189,6 +189,7 @@ import {
} from "./operations/deposits.js"; } from "./operations/deposits.js";
import { import {
acceptExchangeTermsOfService, acceptExchangeTermsOfService,
addPresetExchangeEntry,
downloadTosFromAcceptedFormat, downloadTosFromAcceptedFormat,
getExchangeDetails, getExchangeDetails,
getExchangeRequestTimeout, getExchangeRequestTimeout,
@ -533,6 +534,7 @@ async function fillDefaults(ws: InternalWalletState): Promise<void> {
await tx.auditorTrust.put(c); await tx.auditorTrust.put(c);
} }
for (const baseUrl of ws.config.builtin.exchanges) { for (const baseUrl of ws.config.builtin.exchanges) {
await addPresetExchangeEntry(tx, baseUrl);
const now = AbsoluteTime.now(); const now = AbsoluteTime.now();
provideExchangeRecordInTx(ws, tx, baseUrl, now); provideExchangeRecordInTx(ws, tx, baseUrl, now);
} }
@ -1688,8 +1690,7 @@ export class Wallet {
public static defaultConfig: Readonly<WalletConfig> = { public static defaultConfig: Readonly<WalletConfig> = {
builtin: { builtin: {
//exchanges: ["https://exchange.demo.taler.net/"], exchanges: ["https://exchange.demo.taler.net/"],
exchanges: [],
auditors: [ auditors: [
{ {
currency: "KUDOS", currency: "KUDOS",