From 666b767766ef299bd58655c55ece6d4a82c3e947 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 23 Apr 2024 12:49:42 +0200 Subject: wallet-core: support for withdrawals with forced reserve key --- packages/taler-harness/src/bench1.ts | 4 ++-- packages/taler-harness/src/bench3.ts | 4 ++-- packages/taler-util/src/taler-types.ts | 1 + packages/taler-util/src/wallet-types.ts | 11 ++++++++++ packages/taler-wallet-cli/src/index.ts | 25 ++++++++++++++++++---- .../taler-wallet-core/src/observable-wrappers.ts | 4 ++++ packages/taler-wallet-core/src/shepherd.ts | 23 ++++++++++++++------ packages/taler-wallet-core/src/wallet-api-types.ts | 8 +++++++ packages/taler-wallet-core/src/wallet.ts | 13 +++++++---- packages/taler-wallet-core/src/withdraw.ts | 19 +++++++++++++--- .../taler-wallet-webextension/src/wxBackend.ts | 2 +- 11 files changed, 91 insertions(+), 23 deletions(-) (limited to 'packages') diff --git a/packages/taler-harness/src/bench1.ts b/packages/taler-harness/src/bench1.ts index 216760260..d260ea731 100644 --- a/packages/taler-harness/src/bench1.ts +++ b/packages/taler-harness/src/bench1.ts @@ -74,7 +74,7 @@ export async function runBench1(configJson: any): Promise { // my assumption is that the in-memory db file gets too large if (i % restartWallet == 0) { if (Object.keys(wallet).length !== 0) { - wallet.stop(); + await wallet.client.call(WalletApiOperation.Shutdown, {}); console.log("wallet DB stats", j2s(getDbStats!())); } @@ -127,7 +127,7 @@ export async function runBench1(configJson: any): Promise { } } - wallet.stop(); + await wallet.client.call(WalletApiOperation.Shutdown, {}); console.log("wallet DB stats", j2s(getDbStats!())); } diff --git a/packages/taler-harness/src/bench3.ts b/packages/taler-harness/src/bench3.ts index a5bc094df..ddf763c5b 100644 --- a/packages/taler-harness/src/bench3.ts +++ b/packages/taler-harness/src/bench3.ts @@ -85,7 +85,7 @@ export async function runBench3(configJson: any): Promise { // my assumption is that the in-memory db file gets too large if (i % restartWallet == 0) { if (Object.keys(wallet).length !== 0) { - wallet.stop(); + await wallet.client.call(WalletApiOperation.Shutdown, {}); console.log("wallet DB stats", j2s(getDbStats!())); } @@ -139,7 +139,7 @@ export async function runBench3(configJson: any): Promise { } } - wallet.stop(); + await wallet.client.call(WalletApiOperation.Shutdown, {}); console.log("wallet DB stats", j2s(getDbStats!())); } diff --git a/packages/taler-util/src/taler-types.ts b/packages/taler-util/src/taler-types.ts index 2b8e55e38..392e7149c 100644 --- a/packages/taler-util/src/taler-types.ts +++ b/packages/taler-util/src/taler-types.ts @@ -1335,6 +1335,7 @@ export type AmountString = string & { [__amount_str]: true }; export type Base32String = string; export type EddsaSignatureString = string; export type EddsaPublicKeyString = string; +export type EddsaPrivateKeyString = string; export type CoinPublicKeyString = string; export const codecForDenomination = (): Codec => diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index 0564c45f7..9575e6d7d 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -62,6 +62,7 @@ import { CoinEnvelope, DenomKeyType, DenominationPubKey, + EddsaPrivateKeyString, ExchangeAuditor, ExchangeWireAccount, InternationalizedString, @@ -1843,6 +1844,15 @@ export interface AcceptManualWithdrawalRequest { exchangeBaseUrl: string; amount: AmountString; restrictAge?: number; + + /** + * Instead of generating a fresh, random reserve key pair, + * use the provided reserve private key. + * + * Use with caution. Usage of this field may be restricted + * to developer mode. + */ + forceReservePriv?: EddsaPrivateKeyString; } export const codecForAcceptManualWithdrawalRequest = @@ -1851,6 +1861,7 @@ export const codecForAcceptManualWithdrawalRequest = .property("exchangeBaseUrl", codecForString()) .property("amount", codecForAmountString()) .property("restrictAge", codecOptional(codecForNumber())) + .property("forceReservePriv", codecOptional(codecForString())) .build("AcceptManualWithdrawalRequest"); export interface GetWithdrawalDetailsForAmountRequest { diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts index b85995052..b915de538 100644 --- a/packages/taler-wallet-cli/src/index.ts +++ b/packages/taler-wallet-cli/src/index.ts @@ -57,6 +57,7 @@ import { JsonMessage, runRpcServer } from "@gnu-taler/taler-util/twrpc"; import { AccessStats, createNativeWalletHost2, + nativeCrypto, Wallet, WalletApiOperation, WalletCoreApiClient, @@ -349,7 +350,7 @@ async function withWallet( }, }; const result = await f(ctx); - wh.wallet.stop(); + await wh.wallet.client.call(WalletApiOperation.Shutdown, {}); if (process.env.TALER_WALLET_DBSTATS) { console.log("database stats:"); console.log(j2s(wh.getStats())); @@ -723,6 +724,7 @@ withdrawCli .requiredOption("amount", ["--amount"], clk.AMOUNT, { help: "Amount to withdraw", }) + .maybeOption("forcedReservePriv", ["--forced-reserve-priv"], clk.STRING, {}) .maybeOption("restrictAge", ["--restrict-age"], clk.INT) .action(async (args) => { await withWallet(args, async (wallet) => { @@ -735,7 +737,7 @@ withdrawCli exchangeBaseUrl: exchangeBaseUrl, }, ); - const acct = d.paytoUris[0]; + const acct = d.withdrawalAccountsList[0]; if (!acct) { console.log("exchange has no accounts"); return; @@ -746,10 +748,11 @@ withdrawCli amount, exchangeBaseUrl, restrictAge: parseInt(String(args.withdrawManually.restrictAge), 10), + forceReservePriv: args.withdrawManually.forcedReservePriv, }, ); const reservePub = resp.reservePub; - const completePaytoUri = addPaytoQueryParams(acct, { + const completePaytoUri = addPaytoQueryParams(acct.paytoUri, { amount: args.withdrawManually.amount, message: `Taler top-up ${reservePub}`, }); @@ -1171,6 +1174,20 @@ const advancedCli = walletCli.subcommand("advancedArgs", "advanced", { help: "Subcommands for advanced operations (only use if you know what you're doing!).", }); +advancedCli + .subcommand("genReserve", "gen-reserve", { + help: "Generate a reserve key pair (not stored in the DB).", + }) + .action(async (args) => { + const pair = await nativeCrypto.createEddsaKeypair({}); + console.log( + j2s({ + reservePub: pair.pub, + reservePriv: pair.priv, + }), + ); + }); + advancedCli .subcommand("tasks", "tasks", { help: "Show active wallet-core tasks.", @@ -1322,7 +1339,7 @@ advancedCli merchantBaseUrl: "http://localhost:8083/", }); await wallet.client.call(WalletApiOperation.TestingWaitTasksDone, {}); - wallet.stop(); + await wallet.client.call(WalletApiOperation.Shutdown, {}); }); advancedCli diff --git a/packages/taler-wallet-core/src/observable-wrappers.ts b/packages/taler-wallet-core/src/observable-wrappers.ts index f09498d95..717de41ca 100644 --- a/packages/taler-wallet-core/src/observable-wrappers.ts +++ b/packages/taler-wallet-core/src/observable-wrappers.ts @@ -60,6 +60,10 @@ export class ObservableTaskScheduler implements TaskScheduler { } } + shutdown(): Promise { + return this.impl.shutdown(); + } + getActiveTasks(): TaskIdStr[] { return this.impl.getActiveTasks(); } diff --git a/packages/taler-wallet-core/src/shepherd.ts b/packages/taler-wallet-core/src/shepherd.ts index 5e2d23fd9..3b160d97f 100644 --- a/packages/taler-wallet-core/src/shepherd.ts +++ b/packages/taler-wallet-core/src/shepherd.ts @@ -149,6 +149,7 @@ export interface TaskScheduler { reload(): Promise; getActiveTasks(): TaskIdStr[]; isIdle(): boolean; + shutdown(): Promise; } export class TaskSchedulerImpl implements TaskScheduler { @@ -176,6 +177,14 @@ export class TaskSchedulerImpl implements TaskScheduler { return [...this.sheps.keys()]; } + async shutdown(): Promise { + const tasksIds = [...this.sheps.keys()]; + logger.info(`Stopping task shepherd.`); + for (const taskId of tasksIds) { + this.stopShepherdTask(taskId); + } + } + async ensureRunning(): Promise { if (this.isRunning) { return; @@ -193,7 +202,7 @@ export class TaskSchedulerImpl implements TaskScheduler { logger.error(`err: ${e}`); }) .then(() => { - logger.info("done running task loop"); + logger.trace("done running task loop"); this.isRunning = false; }); } @@ -212,11 +221,11 @@ export class TaskSchedulerImpl implements TaskScheduler { } private async run(): Promise { - logger.info("Running task loop."); - logger.info(`sheps: ${this.sheps.size}`); + logger.trace("Running task loop."); + logger.trace(`sheps: ${this.sheps.size}`); while (true) { if (this.ws.stopped) { - logger.info("Breaking out of task loop (wallet stopped)."); + logger.trace("Breaking out of task loop (wallet stopped)."); break; } @@ -228,7 +237,7 @@ export class TaskSchedulerImpl implements TaskScheduler { await this.iterCond.wait(); } - logger.info("Done with task loop."); + logger.trace("Done with task loop."); } startShepherdTask(taskId: TaskIdStr): void { @@ -360,11 +369,11 @@ export class TaskSchedulerImpl implements TaskScheduler { }; } if (info.cts.token.isCancelled) { - logger.info("task cancelled, not processing result"); + logger.trace("task cancelled, not processing result"); return; } if (this.ws.stopped) { - logger.info("wallet stopped, not processing result"); + logger.trace("wallet stopped, not processing result"); return; } wex.oc.observe({ diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts index ba28c009a..e876d8aea 100644 --- a/packages/taler-wallet-core/src/wallet-api-types.ts +++ b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -252,6 +252,7 @@ export enum WalletApiOperation { AddGlobalCurrencyAuditor = "addGlobalCurrencyAuditor", RemoveGlobalCurrencyAuditor = "removeGlobalCurrencyAuditor", ListAssociatedRefreshes = "listAssociatedRefreshes", + Shutdown = "shutdown", TestingWaitTransactionsFinal = "testingWaitTransactionsFinal", TestingWaitRefreshesFinal = "testingWaitRefreshesFinal", TestingWaitTransactionState = "testingWaitTransactionState", @@ -278,6 +279,12 @@ export type InitWalletOp = { response: InitResponse; }; +export type ShutdownOp = { + op: WalletApiOperation.Shutdown; + request: EmptyObject; + response: EmptyObject; +}; + /** * Change the configuration of wallet-core. * @@ -1284,6 +1291,7 @@ export type WalletOperations = { [WalletApiOperation.TestingListTaskForTransaction]: TestingListTasksForTransactionOp; [WalletApiOperation.TestingGetDenomStats]: TestingGetDenomStatsOp; [WalletApiOperation.TestingPing]: TestingPingOp; + [WalletApiOperation.Shutdown]: ShutdownOp; }; export type WalletCoreRequestType< diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index b59f52840..dd6ce96f4 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -145,6 +145,7 @@ import { parsePaytoUri, parseTalerUri, performanceNow, + safeStringifyException, sampleWalletCoreTransactions, setDangerousTimetravel, validateIban, @@ -909,6 +910,7 @@ async function dispatchRequestInternal( amount: Amounts.parseOrThrow(req.amount), exchangeBaseUrl: req.exchangeBaseUrl, restrictAge: req.restrictAge, + forceReservePriv: req.forceReservePriv, }); return res; } @@ -1432,6 +1434,10 @@ async function dispatchRequestInternal( await applyDevExperiment(wex, req.devExperimentUri); return {}; } + case WalletApiOperation.Shutdown: { + wex.ws.stop(); + return {}; + } case WalletApiOperation.GetVersion: { return getVersion(wex); } @@ -1700,10 +1706,6 @@ export class Wallet { return this.ws.addNotificationListener(f); } - stop(): void { - this.ws.stop(); - } - async handleCoreApiRequest( operation: string, id: string, @@ -1952,6 +1954,9 @@ export class InternalWalletState { this.stopped = true; this.timerGroup.stopCurrentAndFutureTimers(); this.cryptoDispatcher.stop(); + this.taskScheduler.shutdown().catch((e) => { + logger.warn(`shutdown failed: ${safeStringifyException(e)}`); + }); } /** diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index 4936135bd..a55ada796 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -41,6 +41,7 @@ import { DenomSelItem, DenomSelectionState, Duration, + EddsaPrivateKeyString, ExchangeBatchWithdrawRequest, ExchangeUpdateStatus, ExchangeWireAccount, @@ -3050,6 +3051,7 @@ export async function createManualWithdrawal( amount: AmountLike; restrictAge?: number; forcedDenomSel?: ForcedDenomSel; + forceReservePriv?: EddsaPrivateKeyString; }, ): Promise { const { exchangeBaseUrl } = req; @@ -3061,9 +3063,20 @@ export async function createManualWithdrawal( "manual withdrawal with conversion from foreign currency is not yet supported", ); } - const reserveKeyPair: EddsaKeypair = await wex.cryptoApi.createEddsaKeypair( - {}, - ); + + let reserveKeyPair: EddsaKeypair; + if (req.forceReservePriv) { + const pubResp = await wex.cryptoApi.eddsaGetPublic({ + priv: req.forceReservePriv, + }); + + reserveKeyPair = { + priv: req.forceReservePriv, + pub: pubResp.pub, + }; + } else { + reserveKeyPair = await wex.cryptoApi.createEddsaKeypair({}); + } const withdrawalAccountsList = await fetchWithdrawalAccountInfo( wex, diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts b/packages/taler-wallet-webextension/src/wxBackend.ts index bf70f68df..008f80c57 100644 --- a/packages/taler-wallet-webextension/src/wxBackend.ts +++ b/packages/taler-wallet-webextension/src/wxBackend.ts @@ -275,7 +275,7 @@ async function dispatch< async function reinitWallet(): Promise { if (currentWallet) { - currentWallet.stop(); + await currentWallet.client.call(WalletApiOperation.Shutdown, {}); currentWallet = undefined; } currentDatabase = undefined; -- cgit v1.2.3