taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit e0639bf7621f4f65e5b773ec15b651ba2eeb436f
parent b1c49f631179bc4a39b00ece8713b3ebdb624162
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat, 17 Oct 2026 17:06:41 +0200

add new purse expiration limit option

Diffstat:
Mpackages/taler-harness/src/harness/harness.ts | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-harness/src/index.ts | 5+++++
2 files changed, 89 insertions(+), 0 deletions(-)

diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts @@ -862,6 +862,88 @@ function setTalerPaths(config: Configuration, home: string, name?: string) { config.setString("paths", "taler_cache_home", "$TALER_HOME/.config/taler/"); } +/** + * Units of GNUnet's relative time syntax, in microseconds. Only the ones a + * denomination duration is plausibly written in. + */ +const relativeTimeUnits: { [unit: string]: number } = { + us: 1, + ms: 1000, + s: 1000 * 1000, + second: 1000 * 1000, + seconds: 1000 * 1000, + minute: 60 * 1000 * 1000, + minutes: 60 * 1000 * 1000, + hour: 60 * 60 * 1000 * 1000, + hours: 60 * 60 * 1000 * 1000, + day: 24 * 60 * 60 * 1000 * 1000, + days: 24 * 60 * 60 * 1000 * 1000, + week: 7 * 24 * 60 * 60 * 1000 * 1000, + weeks: 7 * 24 * 60 * 60 * 1000 * 1000, + month: 30 * 24 * 60 * 60 * 1000 * 1000, + months: 30 * 24 * 60 * 60 * 1000 * 1000, + year: 365 * 24 * 60 * 60 * 1000 * 1000, + years: 365 * 24 * 60 * 60 * 1000 * 1000, +}; + +/** + * Parse a duration as the exchange's configuration parser reads it. + */ +function parseRelativeTime(s: string): number { + let total = 0; + const re = /([0-9]+)\s*([a-zA-Z]+)/g; + let m: RegExpExecArray | null; + while ((m = re.exec(s)) !== null) { + const unit = relativeTimeUnits[m[2].toLowerCase()]; + if (unit == null) { + throw new Error(`unsupported duration unit "${m[2]}" in "${s}"`); + } + total += Number.parseInt(m[1], 10) * unit; + } + if (total === 0) { + throw new Error(`cannot parse duration "${s}"`); + } + return total; +} + +/** + * A purse refunds the coins deposited into it when it expires, and the + * auditor can only account for that refund while the denomination of the + * coin is still legally valid. The exchange therefore refuses a purse that + * expires after PURSE_EXPIRATION_LIMIT, and refuses to start when that limit + * exceeds the shortest DURATION_LEGAL of any denomination or falls below + * DEFAULT_P2P_EXPIRATION. Derive both from the denominations the test + * configured instead of leaving the exchange to warn about the default. + */ +function setPurseExpirationLimit(config: Configuration): void { + let limit: string | undefined; + let limitUs = Number.MAX_SAFE_INTEGER; + for (const section of config.getSectionNames()) { + if (!section.toLowerCase().startsWith("coin_")) { + continue; + } + const dl = config.getString(section, "duration_legal").getValue(); + if (dl == null) { + continue; + } + const us = parseRelativeTime(dl); + if (us < limitUs) { + limitUs = us; + limit = dl; + } + } + if (limit == null) { + return; + } + config.setString("exchange", "purse_expiration_limit", limit); + const dp2p = config + .getString("exchange", "default_p2p_expiration") + .getValue(); + if (dp2p != null && parseRelativeTime(dp2p) > limitUs) { + config.setString("exchange", "default_p2p_expiration", limit); + } +} + function setExchangeCoin(config: Configuration, c: CoinConfig) { const s = `coin_${c.name}`; config.setString(s, "value", c.value); @@ -1950,6 +2032,7 @@ export class ExchangeService implements ExchangeServiceInterface { offeredCoins.forEach((cc) => setExchangeCoin(config, cc(this.exchangeConfig.currency)), ); + setPurseExpirationLimit(config); config.writeTo(this.configFilename, { excludeDefaults: true }); } @@ -1959,6 +2042,7 @@ export class ExchangeService implements ExchangeServiceInterface { ConfigSources["taler-exchange"], ); ccs.forEach((cc) => setExchangeCoin(config, cc)); + setPurseExpirationLimit(config); config.writeTo(this.configFilename, { excludeDefaults: true }); } diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts @@ -1875,6 +1875,11 @@ deploymentCli out += "# Coin configuration for the exchange.\n"; out += '# Should be placed in "/etc/taler/config.d/exchange-coins.conf".\n'; + out += "#\n"; + out += "# The DURATION_LEGAL below bounds how far in the future a purse\n"; + out += + "# may expire: set PURSE_EXPIRATION_LIMIT in section [exchange] to\n"; + out += "# at most that, or the exchange warns and uses it as the limit.\n"; out += "\n"; while (Amounts.cmp(x, coinMax) < 0) {