separate wallet state from wallet client

This commit is contained in:
Florian Dold 2021-06-15 18:52:43 +02:00
parent 4b16d7bd34
commit d41ae5eb97
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
18 changed files with 1313 additions and 1307 deletions

View File

@ -622,11 +622,13 @@ export const codecForIntegrationTestArgs = (): Codec<IntegrationTestArgs> =>
export interface AddExchangeRequest {
exchangeBaseUrl: string;
forceUpdate?: boolean;
}
export const codecForAddExchangeRequest = (): Codec<AddExchangeRequest> =>
buildCodecForObject<AddExchangeRequest>()
.property("exchangeBaseUrl", codecForString())
.property("forceUpdate", codecOptional(codecForBoolean()))
.build("AddExchangeRequest");
export interface ForceExchangeUpdateRequest {
@ -962,3 +964,15 @@ export const codecForRetryTransactionRequest = (): Codec<RetryTransactionRequest
buildCodecForObject<RetryTransactionRequest>()
.property("transactionId", codecForString())
.build("RetryTransactionRequest");
export interface SetWalletDeviceIdRequest {
/**
* New wallet device ID to set.
*/
walletDeviceId: string;
}
export const codecForSetWalletDeviceIdRequest = (): Codec<SetWalletDeviceIdRequest> =>
buildCodecForObject<SetWalletDeviceIdRequest>()
.property("walletDeviceId", codecForString())
.build("SetWalletDeviceIdRequest");

View File

@ -18,7 +18,6 @@
* Imports.
*/
import {
Wallet,
getDefaultNodeWallet,
DefaultNodeWalletArgs,
NodeHttpLib,
@ -33,7 +32,10 @@ import {
Headers,
WALLET_EXCHANGE_PROTOCOL_VERSION,
WALLET_MERCHANT_PROTOCOL_VERSION,
runRetryLoop,
handleCoreApiRequest,
} from "@gnu-taler/taler-wallet-core";
import { InternalWalletState } from "@gnu-taler/taler-wallet-core/lib/operations/state";
import fs from "fs";
import { WalletNotification } from "../../taler-wallet-core/node_modules/@gnu-taler/taler-util/lib/notifications.js";
@ -154,8 +156,8 @@ function sendAkonoMessage(ev: CoreApiEnvelope): void {
class AndroidWalletMessageHandler {
walletArgs: DefaultNodeWalletArgs | undefined;
maybeWallet: Wallet | undefined;
wp = openPromise<Wallet>();
maybeWallet: InternalWalletState | undefined;
wp = openPromise<InternalWalletState>();
httpLib = new NodeHttpLib();
/**
@ -174,6 +176,17 @@ class AndroidWalletMessageHandler {
result,
};
};
const reinit = async () => {
const w = await getDefaultNodeWallet(this.walletArgs);
this.maybeWallet = w;
await handleCoreApiRequest(w, "initWallet", "akono-init", {});
runRetryLoop(w).catch((e) => {
console.error("Error during wallet retry loop", e);
});
this.wp.resolve(w);
};
switch (operation) {
case "init": {
this.walletArgs = {
@ -183,12 +196,7 @@ class AndroidWalletMessageHandler {
persistentStoragePath: args.persistentStoragePath,
httpLib: this.httpLib,
};
const w = await getDefaultNodeWallet(this.walletArgs);
this.maybeWallet = w;
w.runRetryLoop().catch((e) => {
console.error("Error during wallet retry loop", e);
});
this.wp.resolve(w);
await reinit();
return wrapResponse({
supported_protocol_versions: {
exchange: WALLET_EXCHANGE_PROTOCOL_VERSION,
@ -196,9 +204,6 @@ class AndroidWalletMessageHandler {
},
});
}
case "getHistory": {
return wrapResponse({ history: [] });
}
case "startTunnel": {
// this.httpLib.useNfcTunnel = true;
throw Error("not implemented");
@ -225,19 +230,14 @@ class AndroidWalletMessageHandler {
}
const wallet = await this.wp.promise;
wallet.stop();
this.wp = openPromise<Wallet>();
this.wp = openPromise<InternalWalletState>();
this.maybeWallet = undefined;
const w = await getDefaultNodeWallet(this.walletArgs);
this.maybeWallet = w;
w.runRetryLoop().catch((e) => {
console.error("Error during wallet retry loop", e);
});
this.wp.resolve(w);
await reinit();
return wrapResponse({});
}
default: {
const wallet = await this.wp.promise;
return await wallet.handleCoreApiRequest(operation, id, args);
return await handleCoreApiRequest(wallet, operation, id, args);
}
}
}

View File

@ -33,9 +33,9 @@ import {
codecForList,
codecForString,
Logger,
WithdrawalType,
} from "@gnu-taler/taler-util";
import {
Wallet,
NodeHttpLib,
getDefaultNodeWallet,
OperationFailedAndReportedError,
@ -45,7 +45,14 @@ import {
NodeThreadCryptoWorkerFactory,
CryptoApi,
walletCoreDebugFlags,
WalletCoreApiClient,
WalletApiOperation,
handleCoreApiRequest,
runPending,
runUntilDone,
getClientFromWalletState,
} from "@gnu-taler/taler-wallet-core";
import { InternalWalletState } from "@gnu-taler/taler-wallet-core/src/operations/state";
// This module also serves as the entry point for the crypto
// thread worker, and thus must expose these two handlers.
@ -63,11 +70,13 @@ function assertUnreachable(x: never): never {
}
async function doPay(
wallet: Wallet,
wallet: WalletCoreApiClient,
payUrl: string,
options: { alwaysYes: boolean } = { alwaysYes: true },
): Promise<void> {
const result = await wallet.preparePayForUri(payUrl);
const result = await wallet.call(WalletApiOperation.PreparePayForUri, {
talerPayUri: payUrl,
});
if (result.status === PreparePayResultType.InsufficientBalance) {
console.log("contract", result.contractTerms);
console.error("insufficient balance");
@ -111,7 +120,9 @@ async function doPay(
}
if (pay) {
await wallet.confirmPay(result.proposalId, undefined);
await wallet.call(WalletApiOperation.ConfirmPay, {
proposalId: result.proposalId,
});
} else {
console.log("not paying");
}
@ -161,7 +172,10 @@ type WalletCliArgsType = clk.GetArgType<typeof walletCli>;
async function withWallet<T>(
walletCliArgs: WalletCliArgsType,
f: (w: Wallet) => Promise<T>,
f: (w: {
client: WalletCoreApiClient;
ws: InternalWalletState;
}) => Promise<T>,
): Promise<T> {
const dbPath = walletCliArgs.wallet.walletDbFile ?? defaultWalletDbPath;
const myHttpLib = new NodeHttpLib();
@ -174,8 +188,11 @@ async function withWallet<T>(
});
applyVerbose(walletCliArgs.wallet.verbose);
try {
await wallet.fillDefaults();
const ret = await f(wallet);
const w = {
ws: wallet,
client: await getClientFromWalletState(wallet),
};
const ret = await f(w);
return ret;
} catch (e) {
if (
@ -204,7 +221,10 @@ walletCli
})
.action(async (args) => {
await withWallet(args, async (wallet) => {
const balance = await wallet.getBalances();
const balance = await wallet.client.call(
WalletApiOperation.GetBalances,
{},
);
console.log(JSON.stringify(balance, undefined, 2));
});
});
@ -222,7 +242,8 @@ walletCli
console.error("Invalid JSON");
process.exit(1);
}
const resp = await wallet.handleCoreApiRequest(
const resp = await handleCoreApiRequest(
wallet.ws,
args.api.operation,
"reqid-1",
requestJson,
@ -235,7 +256,10 @@ walletCli
.subcommand("", "pending", { help: "Show pending operations." })
.action(async (args) => {
await withWallet(args, async (wallet) => {
const pending = await wallet.getPendingOperations();
const pending = await wallet.client.call(
WalletApiOperation.GetPendingOperations,
{},
);
console.log(JSON.stringify(pending, undefined, 2));
});
});
@ -246,10 +270,13 @@ walletCli
.maybeOption("search", ["--search"], clk.STRING)
.action(async (args) => {
await withWallet(args, async (wallet) => {
const pending = await wallet.getTransactions({
currency: args.transactions.currency,
search: args.transactions.search,
});
const pending = await wallet.client.call(
WalletApiOperation.GetTransactions,
{
currency: args.transactions.currency,
search: args.transactions.search,
},
);
console.log(JSON.stringify(pending, undefined, 2));
});
});
@ -267,7 +294,20 @@ walletCli
.flag("forceNow", ["-f", "--force-now"])
.action(async (args) => {
await withWallet(args, async (wallet) => {
await wallet.runPending(args.runPendingOpt.forceNow);
await runPending(wallet.ws, args.runPendingOpt.forceNow);
});
});
walletCli
.subcommand("retryTransaction", "retry-transaction", {
help: "Retry a transaction.",
})
.requiredArgument("transactionId", clk.STRING)
.action(async (args) => {
await withWallet(args, async (wallet) => {
await wallet.client.call(WalletApiOperation.RetryTransaction, {
transactionId: args.retryTransaction.transactionId,
});
});
});
@ -278,10 +318,10 @@ walletCli
.maybeOption("maxRetries", ["--max-retries"], clk.INT)
.action(async (args) => {
await withWallet(args, async (wallet) => {
await wallet.runUntilDone({
await runUntilDone(wallet.ws, {
maxRetries: args.finishPendingOpt.maxRetries,
});
wallet.stop();
wallet.ws.stop();
});
});
@ -294,7 +334,7 @@ walletCli
})
.action(async (args) => {
await withWallet(args, async (wallet) => {
await wallet.deleteTransaction({
await wallet.client.call(WalletApiOperation.DeleteTransaction, {
transactionId: args.deleteTransaction.transactionId,
});
});
@ -312,29 +352,51 @@ walletCli
const uriType = classifyTalerUri(uri);
switch (uriType) {
case TalerUriType.TalerPay:
await doPay(wallet, uri, { alwaysYes: args.handleUri.autoYes });
await doPay(wallet.client, uri, {
alwaysYes: args.handleUri.autoYes,
});
break;
case TalerUriType.TalerTip:
{
const res = await wallet.prepareTip(uri);
const res = await wallet.client.call(
WalletApiOperation.PrepareTip,
{
talerTipUri: uri,
},
);
console.log("tip status", res);
await wallet.acceptTip(res.walletTipId);
await wallet.client.call(WalletApiOperation.AcceptTip, {
walletTipId: res.walletTipId,
});
}
break;
case TalerUriType.TalerRefund:
await wallet.applyRefund(uri);
await wallet.client.call(WalletApiOperation.ApplyRefund, {
talerRefundUri: uri,
});
break;
case TalerUriType.TalerWithdraw:
{
const withdrawInfo = await wallet.getWithdrawalDetailsForUri(uri);
const withdrawInfo = await wallet.client.call(
WalletApiOperation.GetWithdrawalDetailsForUri,
{
talerWithdrawUri: uri,
},
);
console.log("withdrawInfo", withdrawInfo);
const selectedExchange = withdrawInfo.defaultExchangeBaseUrl;
if (!selectedExchange) {
console.error("no suggested exchange!");
process.exit(1);
return;
}
const res = await wallet.acceptWithdrawal(uri, selectedExchange);
await wallet.processReserve(res.reservePub);
const res = await wallet.client.call(
WalletApiOperation.AcceptBankIntegratedWithdrawal,
{
exchangeBaseUrl: selectedExchange,
talerWithdrawUri: uri,
},
);
}
break;
default:
@ -356,7 +418,10 @@ exchangesCli
.action(async (args) => {
console.log("Listing exchanges ...");
await withWallet(args, async (wallet) => {
const exchanges = await wallet.getExchanges();
const exchanges = await wallet.client.call(
WalletApiOperation.ListExchanges,
{},
);
console.log(JSON.stringify(exchanges, undefined, 2));
});
});
@ -371,10 +436,10 @@ exchangesCli
.flag("force", ["-f", "--force"])
.action(async (args) => {
await withWallet(args, async (wallet) => {
await wallet.updateExchangeFromUrl(
args.exchangesUpdateCmd.url,
args.exchangesUpdateCmd.force,
);
await wallet.client.call(WalletApiOperation.AddExchange, {
exchangeBaseUrl: args.exchangesUpdateCmd.url,
forceUpdate: args.exchangesUpdateCmd.force,
});
});
});
@ -387,7 +452,9 @@ exchangesCli
})
.action(async (args) => {
await withWallet(args, async (wallet) => {
await wallet.updateExchangeFromUrl(args.exchangesAddCmd.url);
await wallet.client.call(WalletApiOperation.AddExchange, {
exchangeBaseUrl: args.exchangesAddCmd.url,
});
});
});
@ -403,10 +470,10 @@ exchangesCli
})
.action(async (args) => {
await withWallet(args, async (wallet) => {
await wallet.acceptExchangeTermsOfService(
args.exchangesAcceptTosCmd.url,
args.exchangesAcceptTosCmd.etag,
);
await wallet.client.call(WalletApiOperation.SetExchangeTosAccepted, {
etag: args.exchangesAcceptTosCmd.etag,
exchangeBaseUrl: args.exchangesAcceptTosCmd.url,
});
});
});
@ -419,7 +486,12 @@ exchangesCli
})
.action(async (args) => {
await withWallet(args, async (wallet) => {
const tosResult = await wallet.getExchangeTos(args.exchangesTosCmd.url);
const tosResult = await wallet.client.call(
WalletApiOperation.GetExchangeTos,
{
exchangeBaseUrl: args.exchangesTosCmd.url,
},
);
console.log(JSON.stringify(tosResult, undefined, 2));
});
});
@ -435,65 +507,44 @@ backupCli
})
.action(async (args) => {
await withWallet(args, async (wallet) => {
const backup = await wallet.setDeviceId(args.setDeviceId.deviceId);
console.log(JSON.stringify(backup, undefined, 2));
await wallet.client.call(WalletApiOperation.SetWalletDeviceId, {
walletDeviceId: args.setDeviceId.deviceId,
});
});
});
backupCli.subcommand("exportPlain", "export-plain").action(async (args) => {
await withWallet(args, async (wallet) => {
const backup = await wallet.exportBackupPlain();
const backup = await wallet.client.call(
WalletApiOperation.ExportBackupPlain,
{},
);
console.log(JSON.stringify(backup, undefined, 2));
});
});
backupCli
.subcommand("export", "export")
.requiredArgument("filename", clk.STRING, {
help: "backup filename",
})
.action(async (args) => {
await withWallet(args, async (wallet) => {
const backup = await wallet.exportBackupEncrypted();
fs.writeFileSync(args.export.filename, backup);
});
});
backupCli
.subcommand("import", "import")
.requiredArgument("filename", clk.STRING, {
help: "backup filename",
})
.action(async (args) => {
await withWallet(args, async (wallet) => {
const backupEncBlob = fs.readFileSync(args.import.filename);
await wallet.importBackupEncrypted(backupEncBlob);
});
});
backupCli.subcommand("importPlain", "import-plain").action(async (args) => {
await withWallet(args, async (wallet) => {
const data = JSON.parse(await read(process.stdin));
await wallet.importBackupPlain(data);
});
});
backupCli.subcommand("recoverySave", "save-recovery").action(async (args) => {
await withWallet(args, async (wallet) => {
const recoveryJson = await wallet.getBackupRecovery();
const recoveryJson = await wallet.client.call(
WalletApiOperation.ExportBackupRecovery,
{},
);
console.log(JSON.stringify(recoveryJson, undefined, 2));
});
});
backupCli.subcommand("run", "run").action(async (args) => {
await withWallet(args, async (wallet) => {
await wallet.runBackupCycle();
await wallet.client.call(WalletApiOperation.RunBackupCycle, {});
});
});
backupCli.subcommand("status", "status").action(async (args) => {
await withWallet(args, async (wallet) => {
const status = await wallet.getBackupStatus();
const status = await wallet.client.call(
WalletApiOperation.GetBackupInfo,
{},
);
console.log(JSON.stringify(status, undefined, 2));
});
});
@ -518,7 +569,7 @@ backupCli
throw Error("invalid recovery strategy");
}
}
await wallet.loadBackupRecovery({
await wallet.client.call(WalletApiOperation.ImportBackupRecovery, {
recovery: data,
strategy,
});
@ -531,7 +582,7 @@ backupCli
.flag("activate", ["--activate"])
.action(async (args) => {
await withWallet(args, async (wallet) => {
await wallet.addBackupProvider({
await wallet.client.call(WalletApiOperation.AddBackupProvider, {
backupProviderBaseUrl: args.addProvider.url,
activate: args.addProvider.activate,
});
@ -548,12 +599,15 @@ depositCli
.requiredArgument("targetPayto", clk.STRING)
.action(async (args) => {
await withWallet(args, async (wallet) => {
const resp = await wallet.createDepositGroup({
amount: args.createDepositArgs.amount,
depositPaytoUri: args.createDepositArgs.targetPayto,
});
const resp = await wallet.client.call(
WalletApiOperation.CreateDepositGroup,
{
amount: args.createDepositArgs.amount,
depositPaytoUri: args.createDepositArgs.targetPayto,
},
);
console.log(`Created deposit ${resp.depositGroupId}`);
await wallet.runPending();
await runPending(wallet.ws);
});
});
@ -562,9 +616,12 @@ depositCli
.requiredArgument("depositGroupId", clk.STRING)
.action(async (args) => {
await withWallet(args, async (wallet) => {
const resp = await wallet.trackDepositGroup({
depositGroupId: args.trackDepositArgs.depositGroupId,
});
const resp = await wallet.client.call(
WalletApiOperation.TrackDepositGroup,
{
depositGroupId: args.trackDepositArgs.depositGroupId,
},
);
console.log(JSON.stringify(resp, undefined, 2));
});
});
@ -582,9 +639,12 @@ advancedCli
.requiredArgument("amount", clk.STRING)
.action(async (args) => {
await withWallet(args, async (wallet) => {
const details = await wallet.getWithdrawalDetailsForAmount(
args.manualWithdrawalDetails.exchange,
Amounts.parseOrThrow(args.manualWithdrawalDetails.amount),
const details = await wallet.client.call(
WalletApiOperation.GetWithdrawalDetailsForAmount,
{
amount: args.manualWithdrawalDetails.amount,
exchangeBaseUrl: args.manualWithdrawalDetails.exchange,
},
);
console.log(JSON.stringify(details, undefined, 2));
});
@ -611,23 +671,33 @@ advancedCli
})
.action(async (args) => {
await withWallet(args, async (wallet) => {
const { exchange, exchangeDetails } = await wallet.updateExchangeFromUrl(
args.withdrawManually.exchange,
const exchangeBaseUrl = args.withdrawManually.exchange;
const amount = args.withdrawManually.amount;
const d = await wallet.client.call(
WalletApiOperation.GetWithdrawalDetailsForAmount,
{
amount: args.withdrawManually.amount,
exchangeBaseUrl: exchangeBaseUrl,
},
);
const acct = exchangeDetails.wireInfo.accounts[0];
const acct = d.paytoUris[0];
if (!acct) {
console.log("exchange has no accounts");
return;
}
const reserve = await wallet.acceptManualWithdrawal(
exchange.baseUrl,
Amounts.parseOrThrow(args.withdrawManually.amount),
const resp = await wallet.client.call(
WalletApiOperation.AcceptManualWithdrawal,
{
amount,
exchangeBaseUrl,
},
);
const completePaytoUri = addPaytoQueryParams(acct.payto_uri, {
const reservePub = resp.reservePub;
const completePaytoUri = addPaytoQueryParams(acct, {
amount: args.withdrawManually.amount,
message: `Taler top-up ${reserve.reservePub}`,
message: `Taler top-up ${reservePub}`,
});
console.log("Created reserve", reserve.reservePub);
console.log("Created reserve", reservePub);
console.log("Payto URI", completePaytoUri);
});
});
@ -640,37 +710,14 @@ currenciesCli
.subcommand("show", "show", { help: "Show currencies." })
.action(async (args) => {
await withWallet(args, async (wallet) => {
const currencies = await wallet.getCurrencies();
const currencies = await wallet.client.call(
WalletApiOperation.ListCurrencies,
{},
);
console.log(JSON.stringify(currencies, undefined, 2));
});
});
const reservesCli = advancedCli.subcommand("reserves", "reserves", {
help: "Manage reserves.",
});
reservesCli
.subcommand("list", "list", {
help: "List reserves.",
})
.action(async (args) => {
await withWallet(args, async (wallet) => {
const reserves = await wallet.getReservesForExchange();
console.log(JSON.stringify(reserves, undefined, 2));
});
});
reservesCli
.subcommand("update", "update", {
help: "Update reserve status via exchange.",
})
.requiredArgument("reservePub", clk.STRING)
.action(async (args) => {
await withWallet(args, async (wallet) => {
await wallet.updateReserve(args.update.reservePub);
});
});
advancedCli
.subcommand("payPrepare", "pay-prepare", {
help: "Claim an order but don't pay yet.",
@ -678,7 +725,12 @@ advancedCli
.requiredArgument("url", clk.STRING)
.action(async (args) => {
await withWallet(args, async (wallet) => {
const res = await wallet.preparePayForUri(args.payPrepare.url);
const res = await wallet.client.call(
WalletApiOperation.PreparePayForUri,
{
talerPayUri: args.payPrepare.url,
},
);
switch (res.status) {
case PreparePayResultType.InsufficientBalance:
console.log("insufficient balance");
@ -707,10 +759,10 @@ advancedCli
.maybeOption("sessionIdOverride", ["--session-id"], clk.STRING)
.action(async (args) => {
await withWallet(args, async (wallet) => {
wallet.confirmPay(
args.payConfirm.proposalId,
args.payConfirm.sessionIdOverride,
);
await wallet.client.call(WalletApiOperation.ConfirmPay, {
proposalId: args.payConfirm.proposalId,
sessionId: args.payConfirm.sessionIdOverride,
});
});
});
@ -721,7 +773,9 @@ advancedCli
.requiredArgument("coinPub", clk.STRING)
.action(async (args) => {
await withWallet(args, async (wallet) => {
await wallet.refresh(args.refresh.coinPub);
await wallet.client.call(WalletApiOperation.ForceRefresh, {
coinPubList: [args.refresh.coinPub],
});
});
});
@ -731,7 +785,10 @@ advancedCli
})
.action(async (args) => {
await withWallet(args, async (wallet) => {
const coinDump = await wallet.dumpCoins();
const coinDump = await wallet.client.call(
WalletApiOperation.DumpCoins,
{},
);
console.log(JSON.stringify(coinDump, undefined, 2));
});
});
@ -755,7 +812,10 @@ advancedCli
process.exit(1);
}
for (const c of coinPubList) {
await wallet.setCoinSuspended(c, true);
await wallet.client.call(WalletApiOperation.SetCoinSuspended, {
coinPub: c,
suspended: true,
});
}
});
});
@ -777,7 +837,10 @@ advancedCli
process.exit(1);
}
for (const c of coinPubList) {
await wallet.setCoinSuspended(c, false);
await wallet.client.call(WalletApiOperation.SetCoinSuspended, {
coinPub: c,
suspended: false,
});
}
});
});
@ -788,43 +851,18 @@ advancedCli
})
.action(async (args) => {
await withWallet(args, async (wallet) => {
const coins = await wallet.getCoins();
for (const coin of coins) {
console.log(`coin ${coin.coinPub}`);
console.log(` status ${coin.status}`);
console.log(` exchange ${coin.exchangeBaseUrl}`);
console.log(` denomPubHash ${coin.denomPubHash}`);
const coins = await wallet.client.call(WalletApiOperation.DumpCoins, {});
for (const coin of coins.coins) {
console.log(`coin ${coin.coin_pub}`);
console.log(` exchange ${coin.exchange_base_url}`);
console.log(` denomPubHash ${coin.denom_pub_hash}`);
console.log(
` remaining amount ${Amounts.stringify(coin.currentAmount)}`,
` remaining amount ${Amounts.stringify(coin.remaining_value)}`,
);
}
});
});
advancedCli
.subcommand("updateReserve", "update-reserve", {
help: "Update reserve status.",
})
.requiredArgument("reservePub", clk.STRING)
.action(async (args) => {
await withWallet(args, async (wallet) => {
const r = await wallet.updateReserve(args.updateReserve.reservePub);
console.log("updated reserve:", JSON.stringify(r, undefined, 2));
});
});
advancedCli
.subcommand("updateReserve", "show-reserve", {
help: "Show the current reserve status.",
})
.requiredArgument("reservePub", clk.STRING)
.action(async (args) => {
await withWallet(args, async (wallet) => {
const r = await wallet.getReserve(args.updateReserve.reservePub);
console.log("updated reserve:", JSON.stringify(r, undefined, 2));
});
});
const testCli = walletCli.subcommand("testingArgs", "testing", {
help: "Subcommands for testing GNU Taler deployments.",
});

View File

@ -1783,7 +1783,10 @@ export class WalletCli {
}
async forceUpdateExchange(req: ForceExchangeUpdateRequest): Promise<void> {
const resp = await this.apiRequest("forceUpdateExchange", req);
const resp = await this.apiRequest("addExchange", {
exchangeBaseUrl: req.exchangeBaseUrl,
forceUpdate: true,
});
if (resp.type === "response") {
return;
}

View File

@ -213,4 +213,4 @@ export async function runPaymentFaultTest(t: GlobalTestState) {
}
runPaymentFaultTest.suites = ["wallet"];
runPaymentFaultTest.timeoutMs = 120000;
runPaymentFaultTest.timeoutMs = 120000;

View File

@ -576,7 +576,7 @@ export interface ExchangeDetailsRecord {
/**
* Timestamp when the ToS was accepted.
*
*
* Used during backup merging.
*/
termsOfServiceAcceptedTimestamp: Timestamp | undefined;

View File

@ -22,7 +22,6 @@
/**
* Imports.
*/
import { Wallet } from "../wallet";
import {
MemoryBackend,
BridgeIDBFactory,
@ -36,6 +35,7 @@ import { Logger } from "@gnu-taler/taler-util";
import { SynchronousCryptoWorkerFactory } from "../crypto/workers/synchronousWorker";
import type { IDBFactory } from "@gnu-taler/idb-bridge";
import { WalletNotification } from "@gnu-taler/taler-util";
import { InternalWalletState } from "../operations/state.js";
const logger = new Logger("headless/helpers.ts");
@ -93,7 +93,7 @@ function makeId(length: number): string {
*/
export async function getDefaultNodeWallet(
args: DefaultNodeWalletArgs = {},
): Promise<Wallet> {
): Promise<InternalWalletState> {
BridgeIDBFactory.enableTracing = false;
const myBackend = new MemoryBackend();
myBackend.enableTracing = false;
@ -172,7 +172,8 @@ export async function getDefaultNodeWallet(
workerFactory = new SynchronousCryptoWorkerFactory();
}
const w = new Wallet(myDb, myHttpLib, workerFactory);
const w = new InternalWalletState(myDb, myHttpLib, workerFactory);
if (args.notifyHandler) {
w.addNotificationListener(args.notifyHandler);
}

View File

@ -19,34 +19,34 @@
*/
// Errors
export * from "./operations/errors";
export * from "./operations/errors.js";
// Util functionality
export { URL } from "./util/url";
export * from "./util/promiseUtils";
export * from "./util/query";
export * from "./util/http";
export { URL } from "./util/url.js";
export * from "./util/promiseUtils.js";
export * from "./util/query.js";
export * from "./util/http.js";
// Utils for using the wallet under node
export { NodeHttpLib } from "./headless/NodeHttpLib";
export { NodeHttpLib } from "./headless/NodeHttpLib.js";
export {
getDefaultNodeWallet,
DefaultNodeWalletArgs,
} from "./headless/helpers";
} from "./headless/helpers.js";
export * from "./operations/versions";
export * from "./operations/versions.js";
export * from "./db";
export * from "./db.js";
// Crypto and crypto workers
export * from "./crypto/workers/nodeThreadWorker";
export { CryptoImplementation } from "./crypto/workers/cryptoImplementation";
export type { CryptoWorker } from "./crypto/workers/cryptoWorker";
export { CryptoWorkerFactory, CryptoApi } from "./crypto/workers/cryptoApi";
export * from "./crypto/talerCrypto";
export * from "./crypto/workers/nodeThreadWorker.js";
export { CryptoImplementation } from "./crypto/workers/cryptoImplementation.js";
export type { CryptoWorker } from "./crypto/workers/cryptoWorker.js";
export { CryptoWorkerFactory, CryptoApi } from "./crypto/workers/cryptoApi.js";
export * from "./crypto/talerCrypto.js";
export * from "./pending-types";
export * from "./pending-types.js";
export * from "./util/debugFlags";
export * from "./util/debugFlags.js";
export { Wallet } from "./wallet";
export * from "./wallet.js";

View File

@ -38,7 +38,10 @@ import {
WalletBackupConfState,
WALLET_BACKUP_STATE_KEY,
} from "../../db.js";
import { checkDbInvariant, checkLogicInvariant } from "../../util/invariants.js";
import {
checkDbInvariant,
checkLogicInvariant,
} from "../../util/invariants.js";
import {
bytesToString,
decodeCrock,
@ -83,8 +86,15 @@ import {
TalerErrorDetails,
} from "@gnu-taler/taler-util";
import { CryptoApi } from "../../crypto/workers/cryptoApi.js";
import { secretbox, secretbox_open } from "../../crypto/primitives/nacl-fast.js";
import { checkPaymentByProposalId, confirmPay, preparePayForUri } from "../pay.js";
import {
secretbox,
secretbox_open,
} from "../../crypto/primitives/nacl-fast.js";
import {
checkPaymentByProposalId,
confirmPay,
preparePayForUri,
} from "../pay.js";
import { exportBackup } from "./export.js";
import { BackupCryptoPrecomputedData, importBackup } from "./import.js";
import { provideBackupState, getWalletBackupState } from "./state.js";

View File

@ -60,7 +60,12 @@ import {
WALLET_CACHE_BREAKER_CLIENT_VERSION,
WALLET_EXCHANGE_PROTOCOL_VERSION,
} from "./versions.js";
import { getExpiryTimestamp, HttpRequestLibrary, readSuccessResponseJsonOrThrow, readSuccessResponseTextOrThrow } from "../util/http.js";
import {
getExpiryTimestamp,
HttpRequestLibrary,
readSuccessResponseJsonOrThrow,
readSuccessResponseTextOrThrow,
} from "../util/http.js";
import { CryptoApi } from "../crypto/workers/cryptoApi.js";
import { DbAccess, GetReadOnlyAccess } from "../util/query.js";
import { decodeCrock, encodeCrock, hash } from "../crypto/talerCrypto.js";

View File

@ -55,7 +55,10 @@ import { URL } from "../util/url.js";
import { guardOperationException } from "./errors.js";
import { updateExchangeFromUrl } from "./exchanges.js";
import { EXCHANGE_COINS_LOCK, InternalWalletState } from "./state.js";
import { isWithdrawableDenom, selectWithdrawalDenominations } from "./withdraw.js";
import {
isWithdrawableDenom,
selectWithdrawalDenominations,
} from "./withdraw.js";
import { RefreshNewDenomInfo } from "../crypto/cryptoTypes.js";
import { GetReadWriteAccess } from "../util/query.js";

View File

@ -27,8 +27,13 @@ import { WalletStoresV1 } from "../db.js";
import { PendingOperationsResponse } from "../pending-types.js";
import { AsyncOpMemoMap, AsyncOpMemoSingle } from "../util/asyncMemo.js";
import { HttpRequestLibrary } from "../util/http";
import { OpenedPromise, openPromise } from "../util/promiseUtils.js";
import {
AsyncCondition,
OpenedPromise,
openPromise,
} from "../util/promiseUtils.js";
import { DbAccess } from "../util/query.js";
import { TimerGroup } from "../util/timer.js";
type NotificationListener = (n: WalletNotification) => void;
@ -37,6 +42,9 @@ const logger = new Logger("state.ts");
export const EXCHANGE_COINS_LOCK = "exchange-coins-lock";
export const EXCHANGE_RESERVES_LOCK = "exchange-reserves-lock";
/**
* Internal state of the wallet.
*/
export class InternalWalletState {
memoProcessReserve: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
memoMakePlanchet: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
@ -47,8 +55,15 @@ export class InternalWalletState {
memoProcessDeposit: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
cryptoApi: CryptoApi;
timerGroup: TimerGroup = new TimerGroup();
latch = new AsyncCondition();
stopped = false;
memoRunRetryLoop = new AsyncOpMemoSingle<void>();
listeners: NotificationListener[] = [];
initCalled: boolean = false;
/**
* Promises that are waiting for a particular resource.
*/
@ -85,6 +100,15 @@ export class InternalWalletState {
this.listeners.push(f);
}
/**
* Stop ongoing processing.
*/
stop(): void {
this.stopped = true;
this.timerGroup.stopCurrentAndFutureTimers();
this.cryptoApi.stop();
}
/**
* Run an async function after acquiring a list of locks, identified
* by string tokens.

View File

@ -33,10 +33,13 @@ import {
TestPayArgs,
PreparePayResultType,
} from "@gnu-taler/taler-util";
import { Wallet } from "../wallet.js";
import { createTalerWithdrawReserve } from "./reserves.js";
import { InternalWalletState } from "./state.js";
import { URL } from "../util/url.js";
import { confirmPay, preparePayForUri } from "./pay.js";
import { getBalances } from "./balance.js";
import { runUntilDone } from "../wallet.js";
import { applyRefund } from "./refund.js";
const logger = new Logger("operations/testing.ts");
@ -261,14 +264,13 @@ interface BankWithdrawalResponse {
}
async function makePayment(
http: HttpRequestLibrary,
wallet: Wallet,
ws: InternalWalletState,
merchant: MerchantBackendInfo,
amount: string,
summary: string,
): Promise<{ orderId: string }> {
const orderResp = await createOrder(
http,
ws.http,
merchant,
amount,
summary,
@ -277,7 +279,7 @@ async function makePayment(
logger.trace("created order with orderId", orderResp.orderId);
let paymentStatus = await checkPayment(http, merchant, orderResp.orderId);
let paymentStatus = await checkPayment(ws.http, merchant, orderResp.orderId);
logger.trace("payment status", paymentStatus);
@ -286,7 +288,7 @@ async function makePayment(
throw Error("no taler://pay/ URI in payment response");
}
const preparePayResult = await wallet.preparePayForUri(talerPayUri);
const preparePayResult = await preparePayForUri(ws, talerPayUri);
logger.trace("prepare pay result", preparePayResult);
@ -294,14 +296,15 @@ async function makePayment(
throw Error("payment not possible");
}
const confirmPayResult = await wallet.confirmPay(
const confirmPayResult = await confirmPay(
ws,
preparePayResult.proposalId,
undefined,
);
logger.trace("confirmPayResult", confirmPayResult);
paymentStatus = await checkPayment(http, merchant, orderResp.orderId);
paymentStatus = await checkPayment(ws.http, merchant, orderResp.orderId);
logger.trace("payment status after wallet payment:", paymentStatus);
@ -315,8 +318,7 @@ async function makePayment(
}
export async function runIntegrationTest(
http: HttpRequestLibrary,
wallet: Wallet,
ws: InternalWalletState,
args: IntegrationTestArgs,
): Promise<void> {
logger.info("running test with arguments", args);
@ -325,15 +327,16 @@ export async function runIntegrationTest(
const currency = parsedSpendAmount.currency;
logger.info("withdrawing test balance");
await wallet.withdrawTestBalance({
amount: args.amountToWithdraw,
bankBaseUrl: args.bankBaseUrl,
exchangeBaseUrl: args.exchangeBaseUrl,
});
await wallet.runUntilDone();
await withdrawTestBalance(
ws,
args.amountToWithdraw,
args.bankBaseUrl,
args.exchangeBaseUrl,
);
await runUntilDone(ws);
logger.info("done withdrawing test balance");
const balance = await wallet.getBalances();
const balance = await getBalances(ws);
logger.trace(JSON.stringify(balance, null, 2));
@ -342,16 +345,10 @@ export async function runIntegrationTest(
authToken: args.merchantAuthToken,
};
await makePayment(
http,
wallet,
myMerchant,
args.amountToSpend,
"hello world",
);
await makePayment(ws, myMerchant, args.amountToSpend, "hello world");
// Wait until the refresh is done
await wallet.runUntilDone();
await runUntilDone(ws);
logger.trace("withdrawing test balance for refund");
const withdrawAmountTwo = Amounts.parseOrThrow(`${currency}:18`);
@ -359,25 +356,25 @@ export async function runIntegrationTest(
const refundAmount = Amounts.parseOrThrow(`${currency}:6`);
const spendAmountThree = Amounts.parseOrThrow(`${currency}:3`);
await wallet.withdrawTestBalance({
amount: Amounts.stringify(withdrawAmountTwo),
bankBaseUrl: args.bankBaseUrl,
exchangeBaseUrl: args.exchangeBaseUrl,
});
await withdrawTestBalance(
ws,
Amounts.stringify(withdrawAmountTwo),
args.bankBaseUrl,
args.exchangeBaseUrl,
);
// Wait until the withdraw is done
await wallet.runUntilDone();
await runUntilDone(ws);
const { orderId: refundOrderId } = await makePayment(
http,
wallet,
ws,
myMerchant,
Amounts.stringify(spendAmountTwo),
"order that will be refunded",
);
const refundUri = await refund(
http,
ws.http,
myMerchant,
refundOrderId,
"test refund",
@ -386,18 +383,17 @@ export async function runIntegrationTest(
logger.trace("refund URI", refundUri);
await wallet.applyRefund(refundUri);
await applyRefund(ws, refundUri);
logger.trace("integration test: applied refund");
// Wait until the refund is done
await wallet.runUntilDone();
await runUntilDone(ws);
logger.trace("integration test: making payment after refund");
await makePayment(
http,
wallet,
ws,
myMerchant,
Amounts.stringify(spendAmountThree),
"payment after refund",
@ -405,30 +401,26 @@ export async function runIntegrationTest(
logger.trace("integration test: make payment done");
await wallet.runUntilDone();
await runUntilDone(ws);
logger.trace("integration test: all done!");
}
export async function testPay(
http: HttpRequestLibrary,
wallet: Wallet,
args: TestPayArgs,
) {
export async function testPay(ws: InternalWalletState, args: TestPayArgs) {
logger.trace("creating order");
const merchant = {
authToken: args.merchantAuthToken,
baseUrl: args.merchantBaseUrl,
};
const orderResp = await createOrder(
http,
ws.http,
merchant,
args.amount,
args.summary,
"taler://fulfillment-success/thank+you",
);
logger.trace("created new order with order ID", orderResp.orderId);
const checkPayResp = await checkPayment(http, merchant, orderResp.orderId);
const checkPayResp = await checkPayment(ws.http, merchant, orderResp.orderId);
const talerPayUri = checkPayResp.taler_pay_uri;
if (!talerPayUri) {
console.error("fatal: no taler pay URI received from backend");
@ -436,9 +428,9 @@ export async function testPay(
return;
}
logger.trace("taler pay URI:", talerPayUri);
const result = await wallet.preparePayForUri(talerPayUri);
const result = await preparePayForUri(ws, talerPayUri);
if (result.status !== PreparePayResultType.PaymentPossible) {
throw Error(`unexpected prepare pay status: ${result.status}`);
}
await wallet.confirmPay(result.proposalId, undefined);
await confirmPay(ws, result.proposalId, undefined);
}

View File

@ -424,7 +424,7 @@ export async function retryTransaction(
break;
}
case TransactionType.Payment: {
const proposalId = rest[0]
const proposalId = rest[0];
await processPurchasePay(ws, proposalId, true);
break;
}

View File

@ -17,7 +17,7 @@
/**
* Imports.
*/
import test from "ava";
import test from "ava";
import { AmountJson, Amounts } from "@gnu-taler/taler-util";
import { AvailableCoinInfo, selectPayCoins } from "./coinSelection.js";

View File

@ -24,7 +24,10 @@
/**
* Imports
*/
import { OperationFailedError, makeErrorDetails } from "../operations/errors.js";
import {
OperationFailedError,
makeErrorDetails,
} from "../operations/errors.js";
import {
Logger,
Duration,

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,6 @@
import { isFirefox, getPermissionsApi } from "./compat";
import { extendedPermissions } from "./permissions";
import {
Wallet,
OpenedPromise,
openPromise,
openTalerDatabase,
@ -34,6 +33,9 @@ import {
deleteTalerDatabase,
DbAccess,
WalletStoresV1,
handleCoreApiRequest,
runRetryLoop,
handleNotifyReserve,
} from "@gnu-taler/taler-wallet-core";
import {
classifyTalerUri,
@ -45,12 +47,13 @@ import {
} from "@gnu-taler/taler-util";
import { BrowserHttpLib } from "./browserHttpLib";
import { BrowserCryptoWorkerFactory } from "./browserCryptoWorkerFactory";
import { InternalWalletState } from "@gnu-taler/taler-wallet-core/src/operations/state";
/**
* Currently active wallet instance. Might be unloaded and
* re-instantiated when the database is reset.
*/
let currentWallet: Wallet | undefined;
let currentWallet: InternalWalletState | undefined;
let currentDatabase: DbAccess<typeof WalletStoresV1> | undefined;
@ -167,7 +170,7 @@ async function dispatch(
};
break;
}
r = await w.handleCoreApiRequest(req.operation, req.id, req.payload);
r = await handleCoreApiRequest(w, req.operation, req.id, req.payload);
break;
}
}
@ -253,7 +256,7 @@ async function reinitWallet(): Promise<void> {
}
const http = new BrowserHttpLib();
console.log("setting wallet");
const wallet = new Wallet(
const wallet = new InternalWalletState(
currentDatabase,
http,
new BrowserCryptoWorkerFactory(),
@ -267,7 +270,7 @@ async function reinitWallet(): Promise<void> {
}
}
});
wallet.runRetryLoop().catch((e) => {
runRetryLoop(wallet).catch((e) => {
console.log("error during wallet retry loop", e);
});
// Useful for debugging in the background page.
@ -357,7 +360,7 @@ function headerListener(
if (!w) {
return;
}
w.handleNotifyReserve();
handleNotifyReserve(w);
});
break;
default:
@ -448,3 +451,4 @@ export async function wxMain(): Promise<void> {
setupHeaderListener();
});
}