diff options
author | Florian Dold <florian@dold.me> | 2024-02-29 18:37:13 +0100 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2024-02-29 23:24:06 +0100 |
commit | 277a5c641df336d8b5b2d9bd6559e45f0b02aa79 (patch) | |
tree | 49cbdadaf04b5630cdae3e2a89e6d3a74d30632b /packages | |
parent | 206780bb0ee763bcf50a3f4f9f78579a8adcdb3a (diff) |
wallet-core: improve config handling, test builtin exchange config
Diffstat (limited to 'packages')
23 files changed, 366 insertions, 222 deletions
diff --git a/packages/taler-harness/src/bench1.ts b/packages/taler-harness/src/bench1.ts index b78fadf0b..428114e0e 100644 --- a/packages/taler-harness/src/bench1.ts +++ b/packages/taler-harness/src/bench1.ts @@ -29,6 +29,7 @@ import { } from "@gnu-taler/taler-util"; import { AccessStats, + applyRunConfigDefaults, createNativeWalletHost2, Wallet, WalletApiOperation, @@ -82,6 +83,10 @@ export async function runBench1(configJson: any): Promise<void> { // No persistent DB storage. persistentStoragePath: undefined, httpLib: harnessHttpLib, + }); + wallet = res.wallet; + getDbStats = res.getDbStats; + await wallet.client.call(WalletApiOperation.InitWallet, { config: { testing: { insecureTrustExchange: trustExchange, @@ -89,16 +94,13 @@ export async function runBench1(configJson: any): Promise<void> { features: {}, }, }); - wallet = res.wallet; - getDbStats = res.getDbStats; - await wallet.client.call(WalletApiOperation.InitWallet, {}); } logger.trace(`Starting withdrawal amount=${withdrawAmount}`); let start = Date.now(); await wallet.client.call(WalletApiOperation.WithdrawTestBalance, { - amount: b1conf.currency + ":" + withdrawAmount as AmountString, + amount: (b1conf.currency + ":" + withdrawAmount) as AmountString, corebankApiBaseUrl: b1conf.bank, exchangeBaseUrl: b1conf.exchange, }); @@ -117,7 +119,7 @@ export async function runBench1(configJson: any): Promise<void> { start = Date.now(); await wallet.client.call(WalletApiOperation.CreateDepositGroup, { - amount: b1conf.currency + ":10" as AmountString, + amount: (b1conf.currency + ":10") as AmountString, depositPaytoUri: b1conf.payto, }); diff --git a/packages/taler-harness/src/bench2.ts b/packages/taler-harness/src/bench2.ts index c85bd6a5d..90924caec 100644 --- a/packages/taler-harness/src/bench2.ts +++ b/packages/taler-harness/src/bench2.ts @@ -27,6 +27,7 @@ import { } from "@gnu-taler/taler-util"; import { createPlatformHttpLib } from "@gnu-taler/taler-util/http"; import { + applyRunConfigDefaults, CryptoDispatcher, SynchronousCryptoWorkerFactoryPlain, Wallet, @@ -67,6 +68,8 @@ export async function runBench2(configJson: any): Promise<void> { const reserveAmount = (numDeposits + 1) * 10; + const defaultConfig = applyRunConfigDefaults(); + for (let i = 0; i < numIter; i++) { const exchangeInfo = await downloadExchangeInfo(benchConf.exchange, http); @@ -89,7 +92,7 @@ export async function runBench2(configJson: any): Promise<void> { console.log("reserve found"); const d1 = findDenomOrThrow(exchangeInfo, `${curr}:8` as AmountString, { - denomselAllowLate: Wallet.defaultConfig.testing.denomselAllowLate, + denomselAllowLate: defaultConfig.testing.denomselAllowLate, }); for (let j = 0; j < numDeposits; j++) { @@ -118,10 +121,10 @@ export async function runBench2(configJson: any): Promise<void> { const refreshDenoms = [ findDenomOrThrow(exchangeInfo, `${curr}:1` as AmountString, { - denomselAllowLate: Wallet.defaultConfig.testing.denomselAllowLate, + denomselAllowLate: defaultConfig.testing.denomselAllowLate, }), findDenomOrThrow(exchangeInfo, `${curr}:1` as AmountString, { - denomselAllowLate: Wallet.defaultConfig.testing.denomselAllowLate, + denomselAllowLate: defaultConfig.testing.denomselAllowLate, }), ]; diff --git a/packages/taler-harness/src/bench3.ts b/packages/taler-harness/src/bench3.ts index c810f6804..f138dff68 100644 --- a/packages/taler-harness/src/bench3.ts +++ b/packages/taler-harness/src/bench3.ts @@ -93,6 +93,10 @@ export async function runBench3(configJson: any): Promise<void> { // No persistent DB storage. persistentStoragePath: undefined, httpLib: myHttpLib, + }); + wallet = res.wallet; + getDbStats = res.getDbStats; + await wallet.client.call(WalletApiOperation.InitWallet, { config: { features: {}, testing: { @@ -100,16 +104,13 @@ export async function runBench3(configJson: any): Promise<void> { }, }, }); - wallet = res.wallet; - getDbStats = res.getDbStats; - await wallet.client.call(WalletApiOperation.InitWallet, {}); } logger.trace(`Starting withdrawal amount=${withdrawAmount}`); let start = Date.now(); await wallet.client.call(WalletApiOperation.WithdrawTestBalance, { - amount: b3conf.currency + ":" + withdrawAmount as AmountString, + amount: (b3conf.currency + ":" + withdrawAmount) as AmountString, corebankApiBaseUrl: b3conf.bank, exchangeBaseUrl: b3conf.exchange, }); @@ -130,7 +131,7 @@ export async function runBench3(configJson: any): Promise<void> { let payto = b3conf.paytoTemplate.replace("${id}", merchID.toString()); await wallet.client.call(WalletApiOperation.CreateDepositGroup, { - amount: b3conf.currency + ":10" as AmountString, + amount: (b3conf.currency + ":10") as AmountString, depositPaytoUri: payto, }); diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts index 9418a3256..587860547 100644 --- a/packages/taler-harness/src/harness/harness.ts +++ b/packages/taler-harness/src/harness/harness.ts @@ -1961,6 +1961,7 @@ export class WalletService { "serve", "--unix-path", unixPath, + "--no-init", ], `wallet-${this.opts.name}`, ); diff --git a/packages/taler-harness/src/harness/helpers.ts b/packages/taler-harness/src/harness/helpers.ts index f567a87ff..46dc9f9d4 100644 --- a/packages/taler-harness/src/harness/helpers.ts +++ b/packages/taler-harness/src/harness/helpers.ts @@ -31,6 +31,7 @@ import { MerchantApiClient, MerchantContractTerms, NotificationType, + PartialWalletRunConfig, PreparePayResultType, TalerCorebankApiClient, TransactionMajorState, @@ -406,6 +407,7 @@ export interface CreateWalletArgs { name: string; persistent?: boolean; overrideDbPath?: string; + config?: PartialWalletRunConfig; } export async function createWalletDaemonWithClient( @@ -431,8 +433,13 @@ export async function createWalletDaemonWithClient( }, }); await walletClient.connect(); + const defaultRunConfig = { + testing: { + skipDefaults: true, + }, + }; await walletClient.client.call(WalletApiOperation.InitWallet, { - skipDefaults: true, + config: args.config ?? defaultRunConfig, }); return { walletClient, walletService }; diff --git a/packages/taler-harness/src/integrationtests/test-exchange-deposit.ts b/packages/taler-harness/src/integrationtests/test-exchange-deposit.ts index ca8b9e375..05e6e153b 100644 --- a/packages/taler-harness/src/integrationtests/test-exchange-deposit.ts +++ b/packages/taler-harness/src/integrationtests/test-exchange-deposit.ts @@ -28,7 +28,6 @@ import { createPlatformHttpLib } from "@gnu-taler/taler-util/http"; import { CryptoDispatcher, SynchronousCryptoWorkerFactoryPlain, - Wallet, } from "@gnu-taler/taler-wallet-core"; import { checkReserve, @@ -76,9 +75,11 @@ export async function runExchangeDepositTest(t: GlobalTestState) { await checkReserve(http, exchange.baseUrl, reserveKeyPair.pub); - const d1 = findDenomOrThrow(exchangeInfo, "TESTKUDOS:8" as AmountString, { - denomselAllowLate: Wallet.defaultConfig.testing.denomselAllowLate, - }); + const d1 = findDenomOrThrow( + exchangeInfo, + "TESTKUDOS:8" as AmountString, + {}, + ); const coin = await withdrawCoin({ http, diff --git a/packages/taler-harness/src/integrationtests/test-exchange-purse.ts b/packages/taler-harness/src/integrationtests/test-exchange-purse.ts index 8ff740732..83ee13d4e 100644 --- a/packages/taler-harness/src/integrationtests/test-exchange-purse.ts +++ b/packages/taler-harness/src/integrationtests/test-exchange-purse.ts @@ -29,14 +29,12 @@ import { j2s, PeerContractTerms, TalerError, - TalerPreciseTimestamp, } from "@gnu-taler/taler-util"; import { CryptoDispatcher, EncryptContractRequest, SpendCoinDetails, SynchronousCryptoWorkerFactoryPlain, - Wallet, } from "@gnu-taler/taler-wallet-core"; import { checkReserve, @@ -94,9 +92,11 @@ export async function runExchangePurseTest(t: GlobalTestState) { await checkReserve(http, exchange.baseUrl, reserveKeyPair.pub); - const d1 = findDenomOrThrow(exchangeInfo, "TESTKUDOS:8" as AmountString, { - denomselAllowLate: Wallet.defaultConfig.testing.denomselAllowLate, - }); + const d1 = findDenomOrThrow( + exchangeInfo, + "TESTKUDOS:8" as AmountString, + {}, + ); const coin = await withdrawCoin({ http, @@ -110,9 +110,6 @@ export async function runExchangePurseTest(t: GlobalTestState) { }); const amount = "TESTKUDOS:5" as AmountString; - const purseFee = "TESTKUDOS:0"; - - const mergeTimestamp = TalerPreciseTimestamp.now(); const contractTerms: PeerContractTerms = { amount, diff --git a/packages/taler-harness/src/integrationtests/test-kyc.ts b/packages/taler-harness/src/integrationtests/test-kyc.ts index cc756f617..a9ef654fd 100644 --- a/packages/taler-harness/src/integrationtests/test-kyc.ts +++ b/packages/taler-harness/src/integrationtests/test-kyc.ts @@ -18,15 +18,14 @@ * Imports. */ import { - TalerCorebankApiClient, Duration, - j2s, Logger, NotificationType, + TalerCorebankApiClient, TransactionMajorState, TransactionMinorState, TransactionType, - encodeCrock, + j2s, } from "@gnu-taler/taler-util"; import { createPlatformHttpLib } from "@gnu-taler/taler-util/http"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; @@ -35,12 +34,12 @@ import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js"; import { BankService, ExchangeService, - generateRandomPayto, GlobalTestState, MerchantService, - setupDb, WalletClient, WalletService, + generateRandomPayto, + setupDb, } from "../harness/harness.js"; import { EnvOptions, SimpleTestEnvironmentNg } from "../harness/helpers.js"; @@ -199,7 +198,11 @@ export async function createKycTestkudosEnvironment( }); await walletClient.connect(); await walletClient.client.call(WalletApiOperation.InitWallet, { - skipDefaults: true, + config: { + testing: { + skipDefaults: true, + }, + }, }); console.log("setup done!"); diff --git a/packages/taler-harness/src/integrationtests/test-wallet-config.ts b/packages/taler-harness/src/integrationtests/test-wallet-config.ts new file mode 100644 index 000000000..81a723473 --- /dev/null +++ b/packages/taler-harness/src/integrationtests/test-wallet-config.ts @@ -0,0 +1,65 @@ +/* + This file is part of GNU Taler + (C) 2020 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Imports. + */ +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { GlobalTestState } from "../harness/harness.js"; +import { createWalletDaemonWithClient } from "../harness/helpers.js"; + +export async function runWalletConfigTest(t: GlobalTestState) { + const w1 = await createWalletDaemonWithClient(t, { + name: "w1", + config: { + builtin: { + exchanges: [], + }, + }, + }); + + const exchangesResp1 = await w1.walletClient.call( + WalletApiOperation.ListExchanges, + {}, + ); + + t.assertDeepEqual(exchangesResp1.exchanges.length, 0); + + const w2 = await createWalletDaemonWithClient(t, { + name: "w2", + config: { + builtin: { + exchanges: [ + { + exchangeBaseUrl: "https://exchange.demo.taler.net/", + }, + { + exchangeBaseUrl: "https://exchange.test.taler.net/", + }, + ], + }, + }, + }); + + const exchangesResp2 = await w2.walletClient.call( + WalletApiOperation.ListExchanges, + {}, + ); + + t.assertDeepEqual(exchangesResp2.exchanges.length, 2); +} + +runWalletConfigTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts b/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts index 1c4c16e0f..fadb34732 100644 --- a/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts +++ b/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts @@ -25,9 +25,9 @@ import { TalerError, } from "@gnu-taler/taler-util"; import { + applyRunConfigDefaults, CryptoDispatcher, SynchronousCryptoWorkerFactoryPlain, - Wallet, } from "@gnu-taler/taler-wallet-core"; import { checkReserve, @@ -87,8 +87,10 @@ export async function runWalletDblessTest(t: GlobalTestState) { await checkReserve(http, exchange.baseUrl, reserveKeyPair.pub); + const defaultConfig = applyRunConfigDefaults(); + const d1 = findDenomOrThrow(exchangeInfo, "TESTKUDOS:8" as AmountString, { - denomselAllowLate: Wallet.defaultConfig.testing.denomselAllowLate, + denomselAllowLate: defaultConfig.testing.denomselAllowLate, }); const coin = await withdrawCoin({ @@ -131,10 +133,10 @@ export async function runWalletDblessTest(t: GlobalTestState) { const refreshDenoms = [ findDenomOrThrow(exchangeInfo, "TESTKUDOS:1" as AmountString, { - denomselAllowLate: Wallet.defaultConfig.testing.denomselAllowLate, + denomselAllowLate: defaultConfig.testing.denomselAllowLate, }), findDenomOrThrow(exchangeInfo, "TESTKUDOS:1" as AmountString, { - denomselAllowLate: Wallet.defaultConfig.testing.denomselAllowLate, + denomselAllowLate: defaultConfig.testing.denomselAllowLate, }), ]; diff --git a/packages/taler-harness/src/integrationtests/test-wallet-dd48.ts b/packages/taler-harness/src/integrationtests/test-wallet-dd48.ts index 47c96e0e2..3341b6a53 100644 --- a/packages/taler-harness/src/integrationtests/test-wallet-dd48.ts +++ b/packages/taler-harness/src/integrationtests/test-wallet-dd48.ts @@ -17,6 +17,14 @@ /** * Imports. */ +import { + ExchangeEntryStatus, + NotificationType, + TalerError, + TalerErrorCode, + WalletNotification, + j2s, +} from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js"; import { @@ -27,14 +35,6 @@ import { WalletService, setupDb, } from "../harness/harness.js"; -import { - ExchangeEntryStatus, - NotificationType, - TalerError, - TalerErrorCode, - WalletNotification, - j2s, -} from "@gnu-taler/taler-util"; import { withdrawViaBankV2 } from "../harness/helpers.js"; /** @@ -96,7 +96,11 @@ export async function runWalletDd48Test(t: GlobalTestState) { }); await walletClient.connect(); await walletClient.client.call(WalletApiOperation.InitWallet, { - skipDefaults: true, + config: { + testing: { + skipDefaults: true, + }, + }, }); await walletClient.call(WalletApiOperation.AddExchange, { @@ -152,7 +156,7 @@ export async function runWalletDd48Test(t: GlobalTestState) { x.type === NotificationType.ExchangeStateTransition && x.oldExchangeState == null && x.newExchangeState.exchangeEntryStatus === - ExchangeEntryStatus.Ephemeral, + ExchangeEntryStatus.Ephemeral, ), ); diff --git a/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts b/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts index c0c32a008..28b73a9f9 100644 --- a/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts +++ b/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts @@ -119,7 +119,11 @@ export async function runWalletNotificationsTest(t: GlobalTestState) { }); await walletClient.connect(); await walletClient.client.call(WalletApiOperation.InitWallet, { - skipDefaults: true, + config: { + testing: { + skipDefaults: true, + } + } }); const bankAccessApiClient = new TalerCorebankApiClient( diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts index b57d82939..73619a047 100644 --- a/packages/taler-harness/src/integrationtests/testrunner.ts +++ b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -89,6 +89,7 @@ import { runWalletBackupDoublespendTest } from "./test-wallet-backup-doublespend import { runWalletBalanceNotificationsTest } from "./test-wallet-balance-notifications.js"; import { runWalletBalanceTest } from "./test-wallet-balance.js"; import { runWalletCliTerminationTest } from "./test-wallet-cli-termination.js"; +import { runWalletConfigTest } from "./test-wallet-config.js"; import { runWalletCryptoWorkerTest } from "./test-wallet-cryptoworker.js"; import { runWalletDblessTest } from "./test-wallet-dbless.js"; import { runWalletDd48Test } from "./test-wallet-dd48.js"; @@ -196,6 +197,7 @@ const allTests: TestMainFunction[] = [ runOtpTest, runWalletBalanceNotificationsTest, runExchangeManagementTest, + runWalletConfigTest, ]; export interface TestRunSpec { diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index 19bebfb19..4fb9b77e7 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -462,10 +462,60 @@ export interface GetCurrencySpecificationResponse { currencySpecification: CurrencySpecification; } +export interface BuiltinExchange { + exchangeBaseUrl: string; + currencyHint?: string; +} + +export interface PartialWalletRunConfig { + builtin?: Partial<WalletRunConfig["builtin"]>; + testing?: Partial<WalletRunConfig["testing"]>; + features?: Partial<WalletRunConfig["features"]>; +} + +export interface WalletRunConfig { + /** + * Initialization values useful for a complete startup. + * + * These are values may be overridden by different wallets + */ + builtin: { + exchanges: BuiltinExchange[]; + }; + + /** + * Unsafe options which it should only be used to create + * testing environment. + */ + testing: { + /** + * Allow withdrawal of denominations even though they are about to expire. + */ + denomselAllowLate: boolean; + devModeActive: boolean; + insecureTrustExchange: boolean; + preventThrottling: boolean; + skipDefaults: boolean; + emitObservabilityEvents?: boolean; + }; + + /** + * Configurations values that may be safe to show to the user + */ + features: { + allowHttp: boolean; + }; +} + export interface InitRequest { - skipDefaults?: boolean; + config?: PartialWalletRunConfig; } +export const codecForInitRequest = (): Codec<InitRequest> => + buildCodecForObject<InitRequest>() + .property("config", codecForAny()) + .build("InitRequest"); + export interface InitResponse { versionInfo: WalletCoreVersion; } diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts index ffe033b24..773379044 100644 --- a/packages/taler-wallet-cli/src/index.ts +++ b/packages/taler-wallet-cli/src/index.ts @@ -251,6 +251,7 @@ interface CreateWalletResult { async function createLocalWallet( walletCliArgs: WalletCliArgsType, notificationHandler?: (n: WalletNotification) => void, + noInit?: boolean, ): Promise<CreateWalletResult> { const dbPath = walletCliArgs.wallet.walletDbFile ?? defaultWalletDbPath; const myHttpLib = createPlatformHttpLib({ @@ -267,23 +268,29 @@ async function createLocalWallet( } }, cryptoWorkerType: walletCliArgs.wallet.cryptoWorker as any, - config: { - features: {}, - testing: { - devModeActive: checkEnvFlag("TALER_WALLET_DEV_MODE"), - denomselAllowLate: checkEnvFlag( - "TALER_WALLET_DEBUG_DENOMSEL_ALLOW_LATE", - ), - emitObservabilityEvents: observabilityEventFile != null, - skipDefaults: walletCliArgs.wallet.skipDefaults, - }, - }, }); applyVerbose(walletCliArgs.wallet.verbose); + const res = { wallet: wh.wallet, getStats: wh.getDbStats }; + + if (!noInit) { + return res; + } try { - await wh.wallet.handleCoreApiRequest("initWallet", "native-init", {}); - return { wallet: wh.wallet, getStats: wh.getDbStats }; + await wh.wallet.handleCoreApiRequest("initWallet", "native-init", { + config: { + features: {}, + testing: { + devModeActive: checkEnvFlag("TALER_WALLET_DEV_MODE"), + denomselAllowLate: checkEnvFlag( + "TALER_WALLET_DEBUG_DENOMSEL_ALLOW_LATE", + ), + emitObservabilityEvents: observabilityEventFile != null, + skipDefaults: walletCliArgs.wallet.skipDefaults, + }, + }, + }); + return res; } catch (e) { const ed = getErrorDetailFromException(e); console.error("Operation failed: " + summarizeTalerErrorDetail(ed)); @@ -1196,6 +1203,9 @@ advancedCli .requiredOption("unixPath", ["--unix-path"], clk.STRING, { default: "wallet-core.sock", }) + .flag("noInit", ["--no-init"], { + help: "Do not initialize the wallet. The client must send the initWallet message.", + }) .action(async (args) => { logger.info(`serving at ${args.serve.unixPath}`); const onNotif = (notif: WalletNotification) => { diff --git a/packages/taler-wallet-core/src/host-common.ts b/packages/taler-wallet-core/src/host-common.ts index c56d7ed1c..7651e5a12 100644 --- a/packages/taler-wallet-core/src/host-common.ts +++ b/packages/taler-wallet-core/src/host-common.ts @@ -16,7 +16,6 @@ import { WalletNotification } from "@gnu-taler/taler-util"; import { HttpRequestLibrary } from "@gnu-taler/taler-util/http"; -import { WalletConfigParameter } from "./index.js"; /** * Helpers to initiate a wallet in a host environment. @@ -45,11 +44,6 @@ export interface DefaultNodeWalletArgs { httpLib?: HttpRequestLibrary; cryptoWorkerType?: "sync" | "node-worker-thread"; - - /** - * Config parameters - */ - config?: WalletConfigParameter; } /** diff --git a/packages/taler-wallet-core/src/host-impl.node.ts b/packages/taler-wallet-core/src/host-impl.node.ts index a0c739f45..6a32a086a 100644 --- a/packages/taler-wallet-core/src/host-impl.node.ts +++ b/packages/taler-wallet-core/src/host-impl.node.ts @@ -32,7 +32,11 @@ import { shimIndexedDB, } from "@gnu-taler/idb-bridge"; import { createNodeSqlite3Impl } from "@gnu-taler/idb-bridge/node-sqlite3-bindings"; -import { Logger, SetTimeoutTimerAPI } from "@gnu-taler/taler-util"; +import { + Logger, + SetTimeoutTimerAPI, + WalletRunConfig, +} from "@gnu-taler/taler-util"; import { createPlatformHttpLib } from "@gnu-taler/taler-util/http"; import * as fs from "fs"; import { NodeThreadCryptoWorkerFactory } from "./crypto/workers/nodeThreadWorker.js"; @@ -133,15 +137,18 @@ export async function createNativeWalletHost2( wallet: Wallet; getDbStats: () => AccessStats; }> { - let myHttpLib; - if (args.httpLib) { - myHttpLib = args.httpLib; - } else { - myHttpLib = createPlatformHttpLib({ - enableThrottling: true, - requireTls: !args.config?.features?.allowHttp, - }); - } + const myHttpFactory = (config: WalletRunConfig) => { + let myHttpLib; + if (args.httpLib) { + myHttpLib = args.httpLib; + } else { + myHttpLib = createPlatformHttpLib({ + enableThrottling: true, + requireTls: !config.features.allowHttp, + }); + } + return myHttpLib; + }; let dbResp: MakeDbResult; @@ -188,10 +195,9 @@ export async function createNativeWalletHost2( const w = await Wallet.create( myIdbFactory, - myHttpLib, + myHttpFactory, timer, workerFactory, - args.config, ); if (args.notifyHandler) { diff --git a/packages/taler-wallet-core/src/host-impl.qtart.ts b/packages/taler-wallet-core/src/host-impl.qtart.ts index 329e491bf..9c985d0c1 100644 --- a/packages/taler-wallet-core/src/host-impl.qtart.ts +++ b/packages/taler-wallet-core/src/host-impl.qtart.ts @@ -36,7 +36,11 @@ import { createSqliteBackend, shimIndexedDB, } from "@gnu-taler/idb-bridge"; -import { Logger, SetTimeoutTimerAPI } from "@gnu-taler/taler-util"; +import { + Logger, + SetTimeoutTimerAPI, + WalletRunConfig, +} from "@gnu-taler/taler-util"; import { createPlatformHttpLib } from "@gnu-taler/taler-util/http"; import { qjsOs, qjsStd } from "@gnu-taler/taler-util/qtart"; import { SynchronousCryptoWorkerFactoryPlain } from "./crypto/workers/synchronousWorkerFactoryPlain.js"; @@ -180,15 +184,18 @@ export async function createNativeWalletHost2( shimIndexedDB(dbResp.idbFactory); - let myHttpLib; - if (args.httpLib) { - myHttpLib = args.httpLib; - } else { - myHttpLib = createPlatformHttpLib({ - enableThrottling: true, - requireTls: !args.config?.features?.allowHttp, - }); - } + const myHttpFactory = (config: WalletRunConfig) => { + let myHttpLib; + if (args.httpLib) { + myHttpLib = args.httpLib; + } else { + myHttpLib = createPlatformHttpLib({ + enableThrottling: true, + requireTls: !config.features.allowHttp, + }); + } + return myHttpLib; + }; let workerFactory; workerFactory = new SynchronousCryptoWorkerFactoryPlain(); @@ -197,10 +204,9 @@ export async function createNativeWalletHost2( const w = await Wallet.create( myIdbFactory, - myHttpLib, + myHttpFactory, timer, workerFactory, - args.config, ); if (args.notifyHandler) { diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts index 553155ece..240012bca 100644 --- a/packages/taler-wallet-core/src/wallet-api-types.ts +++ b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -277,53 +277,6 @@ export type GetVersionOp = { response: WalletCoreVersion; }; -/** - * Configurations options for the Wallet - * - * All missing values of the config will be replaced with default values - * Default values are defined by Wallet.getDefaultConfig() - */ -export type WalletConfigParameter = RecursivePartial<WalletConfig>; - -export interface BuiltinExchange { - exchangeBaseUrl: string; - currencyHint?: string; -} - -export interface WalletConfig { - /** - * Initialization values useful for a complete startup. - * - * These are values may be overridden by different wallets - */ - builtin: { - exchanges: BuiltinExchange[]; - }; - - /** - * Unsafe options which it should only be used to create - * testing environment. - */ - testing: { - /** - * Allow withdrawal of denominations even though they are about to expire. - */ - denomselAllowLate: boolean; - devModeActive: boolean; - insecureTrustExchange: boolean; - preventThrottling: boolean; - skipDefaults: boolean; - emitObservabilityEvents: boolean; - }; - - /** - * Configurations values that may be safe to show to the user - */ - features: { - allowHttp: boolean; - }; -} - // group: Basic Wallet Information /** @@ -1100,14 +1053,12 @@ export type GetPendingTasksOp = { response: any; }; - export type GetActiveTasksOp = { op: WalletApiOperation.GetActiveTasks; request: EmptyObject; response: GetActiveTasks; }; - /** * Dump all coins of the wallet in a simple JSON format. */ @@ -1305,15 +1256,3 @@ export interface WalletCoreApiClient { payload: WalletCoreRequestType<Op>, ): Promise<WalletCoreResponseType<Op>>; } - -type Primitives = string | number | boolean; - -type RecursivePartial<T extends object> = { - [P in keyof T]?: T[P] extends Array<infer U extends object> - ? Array<RecursivePartial<U>> - : T[P] extends Array<infer J extends Primitives> - ? Array<J> - : T[P] extends object - ? RecursivePartial<T[P]> - : T[P]; -} & object; diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 0dc066730..b397c43d5 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -47,6 +47,7 @@ import { ObservabilityContext, ObservabilityEventType, OpenedPromise, + PartialWalletRunConfig, PrepareWithdrawExchangeRequest, PrepareWithdrawExchangeResponse, RecoverStoredBackupRequest, @@ -64,6 +65,7 @@ import { ValidateIbanResponse, WalletCoreVersion, WalletNotification, + WalletRunConfig, WithdrawalDetailsForAmount, checkDbInvariant, codecForAbortTransaction, @@ -99,6 +101,7 @@ import { codecForGetWithdrawalDetailsForAmountRequest, codecForGetWithdrawalDetailsForUri, codecForImportDbRequest, + codecForInitRequest, codecForInitiatePeerPullPaymentRequest, codecForInitiatePeerPushDebitRequest, codecForIntegrationTestArgs, @@ -277,8 +280,6 @@ import { } from "./versions.js"; import { WalletApiOperation, - WalletConfig, - WalletConfigParameter, WalletCoreApiClient, WalletCoreResponseType, } from "./wallet-api-types.js"; @@ -685,6 +686,17 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( return {}; } case WalletApiOperation.InitWallet: { + const req = codecForInitRequest().decode(payload); + + logger.info(`init request: ${req}`); + + if (wex.ws.initCalled) { + logger.warn( + "initWallet called twice, new run configuration is ignored", + ); + return; + } + logger.trace("initializing wallet"); // Write to the DB to make sure that we're failing early in // case the DB is not writeable. @@ -701,6 +713,9 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( innerError: getErrorDetailFromException(e), }); } + + wex.ws.initWithConfig(applyRunConfigDefaults(req.config)); + wex.ws.initCalled = true; if (wex.ws.config.testing.skipDefaults) { logger.trace("skipping defaults"); @@ -1437,7 +1452,12 @@ export function getNormalWalletExecutionContext( cancellationToken, cryptoApi: ws.cryptoApi, db: ws.db, - http: ws.http, + get http() { + if (ws.initCalled) { + return ws.http; + } + throw Error("wallet not initialized"); + }, taskScheduler: ws.taskScheduler, oc, }; @@ -1456,7 +1476,7 @@ async function handleCoreApiRequest( let wex: WalletExecutionContext; let oc: ObservabilityContext; - if (ws.config.testing.emitObservabilityEvents) { + if (ws.initCalled && ws.config.testing.emitObservabilityEvents) { oc = { observe(evt) { ws.notify({ @@ -1512,6 +1532,34 @@ async function handleCoreApiRequest( } } +export function applyRunConfigDefaults( + wcp?: PartialWalletRunConfig, +): WalletRunConfig { + return { + builtin: { + exchanges: wcp?.builtin?.exchanges ?? [ + { + exchangeBaseUrl: "https://exchange.demo.taler.net/", + currencyHint: "KUDOS", + }, + ], + }, + features: { + allowHttp: wcp?.features?.allowHttp ?? false, + }, + testing: { + denomselAllowLate: wcp?.testing?.denomselAllowLate ?? false, + devModeActive: wcp?.testing?.devModeActive ?? false, + insecureTrustExchange: wcp?.testing?.insecureTrustExchange ?? false, + preventThrottling: wcp?.testing?.preventThrottling ?? false, + skipDefaults: wcp?.testing?.skipDefaults ?? false, + emitObservabilityEvents: wcp?.testing?.emitObservabilityEvents ?? false, + }, + }; +} + +export type HttpFactory = (config: WalletRunConfig) => HttpRequestLibrary; + /** * Public handle to a running wallet. */ @@ -1521,17 +1569,15 @@ export class Wallet { private constructor( idb: IDBFactory, - http: HttpRequestLibrary, + httpFactory: HttpFactory, timer: TimerAPI, cryptoWorkerFactory: CryptoWorkerFactory, - config?: WalletConfigParameter, ) { this.ws = new InternalWalletState( idb, - http, + httpFactory, timer, cryptoWorkerFactory, - Wallet.getEffectiveConfig(config), ); } @@ -1544,44 +1590,15 @@ export class Wallet { static async create( idb: IDBFactory, - http: HttpRequestLibrary, + httpFactory: HttpFactory, timer: TimerAPI, cryptoWorkerFactory: CryptoWorkerFactory, - config?: WalletConfigParameter, ): Promise<Wallet> { - const w = new Wallet(idb, http, timer, cryptoWorkerFactory, config); + const w = new Wallet(idb, httpFactory, timer, cryptoWorkerFactory); w._client = await getClientFromWalletState(w.ws); return w; } - public static defaultConfig: Readonly<WalletConfig> = { - builtin: { - exchanges: [ - { - exchangeBaseUrl: "https://exchange.demo.taler.net/", - currencyHint: "KUDOS", - }, - ], - }, - features: { - allowHttp: false, - }, - testing: { - preventThrottling: false, - devModeActive: false, - insecureTrustExchange: false, - denomselAllowLate: false, - skipDefaults: false, - emitObservabilityEvents: false, - }, - }; - - static getEffectiveConfig( - param?: WalletConfigParameter, - ): Readonly<WalletConfig> { - return deepMerge(Wallet.defaultConfig, param ?? {}); - } - addNotificationListener(f: (n: WalletNotification) => void): CancelFn { return this.ws.addNotificationListener(f); } @@ -1637,10 +1654,12 @@ export class InternalWalletState { taskScheduler: TaskScheduler = new TaskSchedulerImpl(this); - config: Readonly<WalletConfig>; + private _config: Readonly<WalletRunConfig> | undefined; private _db: DbAccess<typeof WalletStoresV1> | undefined = undefined; + private _http: HttpRequestLibrary | undefined = undefined; + get db(): DbAccess<typeof WalletStoresV1> { if (!this._db) { throw Error("db not initialized"); @@ -1648,20 +1667,42 @@ export class InternalWalletState { return this._db; } + initWithConfig(newConfig: WalletRunConfig): void { + if (this._config) { + throw Error("config already set"); + } + this._config = newConfig; + + this._http = this.httpFactory(newConfig); + + if (this.config.testing.devModeActive) { + this._http = new DevExperimentHttpLib(this.http); + } + } + + get config(): WalletRunConfig { + if (!this._config) { + throw Error("config not initialized"); + } + return this._config; + } + + get http(): HttpRequestLibrary { + if (!this._http) { + throw Error("wallet not initialized"); + } + return this._http; + } + constructor( public idb: IDBFactory, - public http: HttpRequestLibrary, + private httpFactory: HttpFactory, public timer: TimerAPI, cryptoWorkerFactory: CryptoWorkerFactory, - configParam: WalletConfig, ) { this.cryptoDispatcher = new CryptoDispatcher(cryptoWorkerFactory); this.cryptoApi = this.cryptoDispatcher.cryptoApi; this.timerGroup = new TimerGroup(timer); - this.config = configParam; - if (this.config.testing.devModeActive) { - this.http = new DevExperimentHttpLib(this.http); - } } async ensureWalletDbOpen(): Promise<void> { diff --git a/packages/taler-wallet-embedded/src/wallet-qjs.ts b/packages/taler-wallet-embedded/src/wallet-qjs.ts index 3023ce79f..384dd3a2a 100644 --- a/packages/taler-wallet-embedded/src/wallet-qjs.ts +++ b/packages/taler-wallet-embedded/src/wallet-qjs.ts @@ -275,14 +275,14 @@ export function installNativeWalletListener(): void { globalThis.installNativeWalletListener = installNativeWalletListener; export async function testWithGv() { - const w = await createNativeWalletHost2({ + const w = await createNativeWalletHost2({}); + await w.wallet.client.call(WalletApiOperation.InitWallet, { config: { features: { allowHttp: true, }, }, }); - await w.wallet.client.call(WalletApiOperation.InitWallet, {}); await w.wallet.client.call(WalletApiOperation.RunIntegrationTest, { amountToSpend: "KUDOS:1" as AmountString, amountToWithdraw: "KUDOS:3" as AmountString, @@ -297,14 +297,14 @@ export async function testWithGv() { } export async function testWithFdold() { - const w = await createNativeWalletHost2({ + const w = await createNativeWalletHost2({}); + await w.wallet.client.call(WalletApiOperation.InitWallet, { config: { features: { allowHttp: true, }, }, }); - await w.wallet.client.call(WalletApiOperation.InitWallet, {}); await w.wallet.client.call(WalletApiOperation.RunIntegrationTest, { amountToSpend: "TESTKUDOS:1" as AmountString, amountToWithdraw: "TESTKUDOS:3" as AmountString, @@ -321,16 +321,18 @@ export async function testWithLocal(path: string) { console.log("running local test"); const w = await createNativeWalletHost2({ persistentStoragePath: path ?? "walletdb.json", + }); + console.log("created wallet"); + await w.wallet.client.call(WalletApiOperation.InitWallet, { config: { features: { allowHttp: true, }, + testing: { + skipDefaults: true, + }, }, }); - console.log("created wallet"); - await w.wallet.client.call(WalletApiOperation.InitWallet, { - skipDefaults: true, - }); console.log("initialized wallet"); await w.wallet.client.call(WalletApiOperation.RunIntegrationTest, { amountToSpend: "TESTKUDOS:1" as AmountString, diff --git a/packages/taler-wallet-webextension/src/platform/api.ts b/packages/taler-wallet-webextension/src/platform/api.ts index faf0f2820..57e7c51f4 100644 --- a/packages/taler-wallet-webextension/src/platform/api.ts +++ b/packages/taler-wallet-webextension/src/platform/api.ts @@ -17,10 +17,10 @@ import { CoreApiResponse, TalerUri, - WalletNotification + WalletNotification, + WalletRunConfig } from "@gnu-taler/taler-util"; import { - WalletConfig, WalletOperations } from "@gnu-taler/taler-wallet-core"; import { @@ -108,7 +108,7 @@ export interface WalletWebExVersion { version: string; } -type F = WalletConfig["features"]; +type F = WalletRunConfig["features"]; type kf = keyof F; type WebexWalletConfig = { [P in keyof F as `wallet${Capitalize<P>}`]: F[P]; diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts b/packages/taler-wallet-webextension/src/wxBackend.ts index 2753d57b4..0801d2ab0 100644 --- a/packages/taler-wallet-webextension/src/wxBackend.ts +++ b/packages/taler-wallet-webextension/src/wxBackend.ts @@ -248,20 +248,25 @@ async function reinitWallet(): Promise<void> { } currentDatabase = undefined; // setBadgeText({ text: "" }); - let httpLib: HttpRequestLibrary; let cryptoWorker; let timer; + const httpFactory = (): HttpRequestLibrary => { + if (platform.useServiceWorkerAsBackgroundProcess()) { + return new ServiceWorkerHttpLib({ + // enableThrottling: false, + }); + } else { + return new BrowserHttpLib({ + // enableThrottling: false, + }); + } + }; + if (platform.useServiceWorkerAsBackgroundProcess()) { - httpLib = new ServiceWorkerHttpLib({ - // enableThrottling: false, - }); cryptoWorker = new SynchronousCryptoWorkerFactoryPlain(); timer = new SetTimeoutTimerAPI(); } else { - httpLib = new BrowserHttpLib({ - // enableThrottling: false, - }); // We could (should?) use the BrowserCryptoWorkerFactory here, // but right now we don't, to have less platform differences. // cryptoWorker = new BrowserCryptoWorkerFactory(); @@ -273,20 +278,19 @@ async function reinitWallet(): Promise<void> { logger.info("Setting up wallet"); const wallet = await Wallet.create( indexedDB as any, - httpLib as any, + httpFactory as any, timer, cryptoWorker, - { + ); + try { + await wallet.handleCoreApiRequest("initWallet", "native-init", { testing: { emitObservabilityEvents: true, }, features: { allowHttp: settings.walletAllowHttp, }, - }, - ); - try { - await wallet.handleCoreApiRequest("initWallet", "native-init", {}); + }); } catch (e) { logger.error("could not initialize wallet", e); walletInit.reject(e); |