aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2024-04-23 12:49:42 +0200
committerFlorian Dold <florian@dold.me>2024-04-23 12:49:42 +0200
commit666b767766ef299bd58655c55ece6d4a82c3e947 (patch)
treead55f617626d96fb656ff7ab4ccb71b7931ee7b9
parent0308290a128d29792225d7b2f4d2e22cb5d42f72 (diff)
wallet-core: support for withdrawals with forced reserve key
-rw-r--r--packages/taler-harness/src/bench1.ts4
-rw-r--r--packages/taler-harness/src/bench3.ts4
-rw-r--r--packages/taler-util/src/taler-types.ts1
-rw-r--r--packages/taler-util/src/wallet-types.ts11
-rw-r--r--packages/taler-wallet-cli/src/index.ts25
-rw-r--r--packages/taler-wallet-core/src/observable-wrappers.ts4
-rw-r--r--packages/taler-wallet-core/src/shepherd.ts23
-rw-r--r--packages/taler-wallet-core/src/wallet-api-types.ts8
-rw-r--r--packages/taler-wallet-core/src/wallet.ts13
-rw-r--r--packages/taler-wallet-core/src/withdraw.ts19
-rw-r--r--packages/taler-wallet-webextension/src/wxBackend.ts2
11 files changed, 91 insertions, 23 deletions
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<void> {
// 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<void> {
}
}
- 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<void> {
// 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<void> {
}
}
- 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<ExchangeDenomination> =>
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<T>(
},
};
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}`,
});
@@ -1172,6 +1175,20 @@ const advancedCli = walletCli.subcommand("advancedArgs", "advanced", {
});
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<void> {
+ 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<void>;
getActiveTasks(): TaskIdStr[];
isIdle(): boolean;
+ shutdown(): Promise<void>;
}
export class TaskSchedulerImpl implements TaskScheduler {
@@ -176,6 +177,14 @@ export class TaskSchedulerImpl implements TaskScheduler {
return [...this.sheps.keys()];
}
+ async shutdown(): Promise<void> {
+ const tasksIds = [...this.sheps.keys()];
+ logger.info(`Stopping task shepherd.`);
+ for (const taskId of tasksIds) {
+ this.stopShepherdTask(taskId);
+ }
+ }
+
async ensureRunning(): Promise<void> {
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<void> {
- 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<AcceptManualWithdrawalResult> {
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<void> {
if (currentWallet) {
- currentWallet.stop();
+ await currentWallet.client.call(WalletApiOperation.Shutdown, {});
currentWallet = undefined;
}
currentDatabase = undefined;