helpers for auditor integration test

This commit is contained in:
Florian Dold 2020-03-24 15:25:04 +05:30
parent c4d2899562
commit 01e83df471
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
8 changed files with 206 additions and 67 deletions

View File

@ -32,6 +32,7 @@ import { classifyTalerUri, TalerUriType } from "../util/taleruri";
import util = require("util"); import util = require("util");
import { Configuration } from "../util/talerconfig"; import { Configuration } from "../util/talerconfig";
import { setDangerousTimetravel } from "../util/time"; import { setDangerousTimetravel } from "../util/time";
import { makeCodecForList, codecForString } from "../util/codec";
// Backwards compatibility with nodejs<0.11, where TextEncoder and TextDecoder // Backwards compatibility with nodejs<0.11, where TextEncoder and TextDecoder
// are not globals yet. // are not globals yet.
@ -118,7 +119,7 @@ const walletCli = clk
help: "Command line interface for the GNU Taler wallet.", help: "Command line interface for the GNU Taler wallet.",
}) })
.maybeOption("walletDbFile", ["--wallet-db"], clk.STRING, { .maybeOption("walletDbFile", ["--wallet-db"], clk.STRING, {
help: "location of the wallet database file" help: "location of the wallet database file",
}) })
.maybeOption("timetravel", ["--timetravel"], clk.INT, { .maybeOption("timetravel", ["--timetravel"], clk.INT, {
help: "modify system time by given offset in microseconds", help: "modify system time by given offset in microseconds",
@ -172,8 +173,8 @@ walletCli
.flag("json", ["--json"], { .flag("json", ["--json"], {
help: "Show raw JSON.", help: "Show raw JSON.",
}) })
.action(async args => { .action(async (args) => {
await withWallet(args, async wallet => { await withWallet(args, async (wallet) => {
const balance = await wallet.getBalances(); const balance = await wallet.getBalances();
if (args.balance.json) { if (args.balance.json) {
console.log(JSON.stringify(balance, undefined, 2)); console.log(JSON.stringify(balance, undefined, 2));
@ -195,8 +196,8 @@ walletCli
.maybeOption("to", ["--to"], clk.STRING) .maybeOption("to", ["--to"], clk.STRING)
.maybeOption("limit", ["--limit"], clk.STRING) .maybeOption("limit", ["--limit"], clk.STRING)
.maybeOption("contEvt", ["--continue-with"], clk.STRING) .maybeOption("contEvt", ["--continue-with"], clk.STRING)
.action(async args => { .action(async (args) => {
await withWallet(args, async wallet => { await withWallet(args, async (wallet) => {
const history = await wallet.getHistory(); const history = await wallet.getHistory();
if (args.history.json) { if (args.history.json) {
console.log(JSON.stringify(history, undefined, 2)); console.log(JSON.stringify(history, undefined, 2));
@ -216,8 +217,8 @@ walletCli
walletCli walletCli
.subcommand("", "pending", { help: "Show pending operations." }) .subcommand("", "pending", { help: "Show pending operations." })
.action(async args => { .action(async (args) => {
await withWallet(args, async wallet => { await withWallet(args, async (wallet) => {
const pending = await wallet.getPendingOperations(); const pending = await wallet.getPendingOperations();
console.log(JSON.stringify(pending, undefined, 2)); console.log(JSON.stringify(pending, undefined, 2));
}); });
@ -234,8 +235,8 @@ walletCli
help: "Run pending operations.", help: "Run pending operations.",
}) })
.flag("forceNow", ["-f", "--force-now"]) .flag("forceNow", ["-f", "--force-now"])
.action(async args => { .action(async (args) => {
await withWallet(args, async wallet => { await withWallet(args, async (wallet) => {
await wallet.runPending(args.runPendingOpt.forceNow); await wallet.runPending(args.runPendingOpt.forceNow);
}); });
}); });
@ -246,8 +247,8 @@ walletCli
}) })
.requiredArgument("uri", clk.STRING) .requiredArgument("uri", clk.STRING)
.flag("autoYes", ["-y", "--yes"]) .flag("autoYes", ["-y", "--yes"])
.action(async args => { .action(async (args) => {
await withWallet(args, async wallet => { await withWallet(args, async (wallet) => {
const uri: string = args.handleUri.uri; const uri: string = args.handleUri.uri;
const uriType = classifyTalerUri(uri); const uriType = classifyTalerUri(uri);
switch (uriType) { switch (uriType) {
@ -294,9 +295,9 @@ exchangesCli
.subcommand("exchangesListCmd", "list", { .subcommand("exchangesListCmd", "list", {
help: "List known exchanges.", help: "List known exchanges.",
}) })
.action(async args => { .action(async (args) => {
console.log("Listing exchanges ..."); console.log("Listing exchanges ...");
await withWallet(args, async wallet => { await withWallet(args, async (wallet) => {
const exchanges = await wallet.getExchanges(); const exchanges = await wallet.getExchanges();
console.log("exchanges", exchanges); console.log("exchanges", exchanges);
}); });
@ -310,8 +311,8 @@ exchangesCli
help: "Base URL of the exchange.", help: "Base URL of the exchange.",
}) })
.flag("force", ["-f", "--force"]) .flag("force", ["-f", "--force"])
.action(async args => { .action(async (args) => {
await withWallet(args, async wallet => { await withWallet(args, async (wallet) => {
const res = await wallet.updateExchangeFromUrl( const res = await wallet.updateExchangeFromUrl(
args.exchangesUpdateCmd.url, args.exchangesUpdateCmd.url,
args.exchangesUpdateCmd.force, args.exchangesUpdateCmd.force,
@ -328,7 +329,7 @@ advancedCli
.subcommand("decode", "decode", { .subcommand("decode", "decode", {
help: "Decode base32-crockford.", help: "Decode base32-crockford.",
}) })
.action(args => { .action((args) => {
const enc = fs.readFileSync(0, "utf8"); const enc = fs.readFileSync(0, "utf8");
fs.writeFileSync(1, decodeCrock(enc.trim())); fs.writeFileSync(1, decodeCrock(enc.trim()));
}); });
@ -338,8 +339,8 @@ advancedCli
help: "Claim an order but don't pay yet.", help: "Claim an order but don't pay yet.",
}) })
.requiredArgument("url", clk.STRING) .requiredArgument("url", clk.STRING)
.action(async args => { .action(async (args) => {
await withWallet(args, async wallet => { await withWallet(args, async (wallet) => {
const res = await wallet.preparePayForUri(args.payPrepare.url); const res = await wallet.preparePayForUri(args.payPrepare.url);
switch (res.status) { switch (res.status) {
case "error": case "error":
@ -365,18 +366,75 @@ advancedCli
help: "Force a refresh on a coin.", help: "Force a refresh on a coin.",
}) })
.requiredArgument("coinPub", clk.STRING) .requiredArgument("coinPub", clk.STRING)
.action(async args => { .action(async (args) => {
await withWallet(args, async wallet => { await withWallet(args, async (wallet) => {
await wallet.refresh(args.refresh.coinPub); await wallet.refresh(args.refresh.coinPub);
}); });
}); });
advancedCli
.subcommand("dumpCoins", "dump-coins", {
help: "Dump coins in an easy-to-process format.",
})
.action(async (args) => {
await withWallet(args, async (wallet) => {
const coinDump = await wallet.dumpCoins();
console.log(JSON.stringify(coinDump, undefined, 2));
});
});
const coinPubListCodec = makeCodecForList(codecForString);
advancedCli
.subcommand("suspendCoins", "suspend-coins", {
help: "Mark a coin as suspended, will not be used for payments.",
})
.requiredArgument("coinPubSpec", clk.STRING)
.action(async (args) => {
await withWallet(args, async (wallet) => {
let coinPubList: string[];
try {
coinPubList = coinPubListCodec.decode(
JSON.parse(args.suspendCoins.coinPubSpec),
);
} catch (e) {
console.log("could not parse coin list:", e.message);
process.exit(1);
}
for (const c of coinPubList) {
await wallet.setCoinSuspended(c, true);
}
});
});
advancedCli
.subcommand("unsuspendCoins", "unsuspend-coins", {
help: "Mark a coin as suspended, will not be used for payments.",
})
.requiredArgument("coinPubSpec", clk.STRING)
.action(async (args) => {
await withWallet(args, async (wallet) => {
let coinPubList: string[];
try {
coinPubList = coinPubListCodec.decode(
JSON.parse(args.unsuspendCoins.coinPubSpec),
);
} catch (e) {
console.log("could not parse coin list:", e.message);
process.exit(1);
}
for (const c of coinPubList) {
await wallet.setCoinSuspended(c, false);
}
});
});
advancedCli advancedCli
.subcommand("coins", "list-coins", { .subcommand("coins", "list-coins", {
help: "List coins.", help: "List coins.",
}) })
.action(async args => { .action(async (args) => {
await withWallet(args, async wallet => { await withWallet(args, async (wallet) => {
const coins = await wallet.getCoins(); const coins = await wallet.getCoins();
for (const coin of coins) { for (const coin of coins) {
console.log(`coin ${coin.coinPub}`); console.log(`coin ${coin.coinPub}`);
@ -395,8 +453,8 @@ advancedCli
help: "Update reserve status.", help: "Update reserve status.",
}) })
.requiredArgument("reservePub", clk.STRING) .requiredArgument("reservePub", clk.STRING)
.action(async args => { .action(async (args) => {
await withWallet(args, async wallet => { await withWallet(args, async (wallet) => {
const r = await wallet.updateReserve(args.updateReserve.reservePub); const r = await wallet.updateReserve(args.updateReserve.reservePub);
console.log("updated reserve:", JSON.stringify(r, undefined, 2)); console.log("updated reserve:", JSON.stringify(r, undefined, 2));
}); });
@ -407,8 +465,8 @@ advancedCli
help: "Show the current reserve status.", help: "Show the current reserve status.",
}) })
.requiredArgument("reservePub", clk.STRING) .requiredArgument("reservePub", clk.STRING)
.action(async args => { .action(async (args) => {
await withWallet(args, async wallet => { await withWallet(args, async (wallet) => {
const r = await wallet.getReserve(args.updateReserve.reservePub); const r = await wallet.getReserve(args.updateReserve.reservePub);
console.log("updated reserve:", JSON.stringify(r, undefined, 2)); console.log("updated reserve:", JSON.stringify(r, undefined, 2));
}); });
@ -421,7 +479,7 @@ const testCli = walletCli.subcommand("testingArgs", "testing", {
testCli testCli
.subcommand("integrationtestBasic", "integrationtest-basic") .subcommand("integrationtestBasic", "integrationtest-basic")
.requiredArgument("cfgfile", clk.STRING) .requiredArgument("cfgfile", clk.STRING)
.action(async args => { .action(async (args) => {
const cfgStr = fs.readFileSync(args.integrationtestBasic.cfgfile, "utf8"); const cfgStr = fs.readFileSync(args.integrationtestBasic.cfgfile, "utf8");
const cfg = new Configuration(); const cfg = new Configuration();
cfg.loadFromString(cfgStr); cfg.loadFromString(cfgStr);
@ -429,7 +487,7 @@ testCli
await runIntegrationTestBasic(cfg); await runIntegrationTestBasic(cfg);
} catch (e) { } catch (e) {
console.log("integration test failed"); console.log("integration test failed");
console.log(e) console.log(e);
process.exit(1); process.exit(1);
} }
process.exit(0); process.exit(0);
@ -441,7 +499,7 @@ testCli
.requiredOption("summary", ["-s", "--summary"], clk.STRING, { .requiredOption("summary", ["-s", "--summary"], clk.STRING, {
default: "Test Payment", default: "Test Payment",
}) })
.action(async args => { .action(async (args) => {
const cmdArgs = args.testPayCmd; const cmdArgs = args.testPayCmd;
console.log("creating order"); console.log("creating order");
const merchantBackend = new MerchantBackendConnection( const merchantBackend = new MerchantBackendConnection(
@ -462,7 +520,7 @@ testCli
return; return;
} }
console.log("taler pay URI:", talerPayUri); console.log("taler pay URI:", talerPayUri);
await withWallet(args, async wallet => { await withWallet(args, async (wallet) => {
await doPay(wallet, talerPayUri, { alwaysYes: true }); await doPay(wallet, talerPayUri, { alwaysYes: true });
}); });
}); });
@ -489,7 +547,7 @@ testCli
.requiredOption("spendAmount", ["-s", "--spend-amount"], clk.STRING, { .requiredOption("spendAmount", ["-s", "--spend-amount"], clk.STRING, {
default: "TESTKUDOS:4", default: "TESTKUDOS:4",
}) })
.action(async args => { .action(async (args) => {
applyVerbose(args.wallet.verbose); applyVerbose(args.wallet.verbose);
let cmdObj = args.integrationtestCmd; let cmdObj = args.integrationtestCmd;
@ -501,7 +559,7 @@ testCli
exchangeBaseUrl: cmdObj.exchange, exchangeBaseUrl: cmdObj.exchange,
merchantApiKey: cmdObj.merchantApiKey, merchantApiKey: cmdObj.merchantApiKey,
merchantBaseUrl: cmdObj.merchant, merchantBaseUrl: cmdObj.merchant,
}).catch(err => { }).catch((err) => {
console.error("Integration test failed with exception:"); console.error("Integration test failed with exception:");
console.error(err); console.error(err);
process.exit(1); process.exit(1);
@ -520,7 +578,7 @@ testCli
.requiredOption("amount", ["-a", "--amount"], clk.STRING, { .requiredOption("amount", ["-a", "--amount"], clk.STRING, {
default: "TESTKUDOS:10", default: "TESTKUDOS:10",
}) })
.action(async args => { .action(async (args) => {
const merchantBackend = new MerchantBackendConnection( const merchantBackend = new MerchantBackendConnection(
"https://backend.test.taler.net/", "https://backend.test.taler.net/",
"sandbox", "sandbox",
@ -539,7 +597,7 @@ testCli
.requiredOption("bank", ["-b", "--bank"], clk.STRING, { .requiredOption("bank", ["-b", "--bank"], clk.STRING, {
default: "https://bank.test.taler.net/", default: "https://bank.test.taler.net/",
}) })
.action(async args => { .action(async (args) => {
const b = new Bank(args.genWithdrawUri.bank); const b = new Bank(args.genWithdrawUri.bank);
const user = await b.registerRandomUser(); const user = await b.registerRandomUser();
const url = await b.generateWithdrawUri(user, args.genWithdrawUri.amount); const url = await b.generateWithdrawUri(user, args.genWithdrawUri.amount);
@ -559,7 +617,7 @@ testCli
.requiredOption("summary", ["-s", "--summary"], clk.STRING, { .requiredOption("summary", ["-s", "--summary"], clk.STRING, {
default: "Test Payment (for refund)", default: "Test Payment (for refund)",
}) })
.action(async args => { .action(async (args) => {
const cmdArgs = args.genRefundUri; const cmdArgs = args.genRefundUri;
const merchantBackend = new MerchantBackendConnection( const merchantBackend = new MerchantBackendConnection(
"https://backend.test.taler.net/", "https://backend.test.taler.net/",
@ -578,7 +636,7 @@ testCli
process.exit(1); process.exit(1);
return; return;
} }
await withWallet(args, async wallet => { await withWallet(args, async (wallet) => {
await doPay(wallet, talerPayUri, { alwaysYes: true }); await doPay(wallet, talerPayUri, { alwaysYes: true });
}); });
const refundUri = await merchantBackend.refund( const refundUri = await merchantBackend.refund(
@ -611,7 +669,7 @@ testCli
.requiredOption("merchantApiKey", ["-k", "--merchant-api-key"], clk.STRING, { .requiredOption("merchantApiKey", ["-k", "--merchant-api-key"], clk.STRING, {
default: "sandbox", default: "sandbox",
}) })
.action(async args => { .action(async (args) => {
const cmdArgs = args.genPayUri; const cmdArgs = args.genPayUri;
console.log("creating order"); console.log("creating order");
const merchantBackend = new MerchantBackendConnection( const merchantBackend = new MerchantBackendConnection(
@ -669,8 +727,8 @@ testCli
default: "https://bank.test.taler.net/", default: "https://bank.test.taler.net/",
help: "Bank base URL", help: "Bank base URL",
}) })
.action(async args => { .action(async (args) => {
await withWallet(args, async wallet => { await withWallet(args, async (wallet) => {
await withdrawTestBalance( await withdrawTestBalance(
wallet, wallet,
args.withdrawArgs.amount, args.withdrawArgs.amount,

View File

@ -318,10 +318,6 @@ async function getCoinsForPayment(
.iterIndex(Stores.coins.exchangeBaseUrlIndex, exchange.baseUrl) .iterIndex(Stores.coins.exchangeBaseUrlIndex, exchange.baseUrl)
.toArray(); .toArray();
const denoms = await ws.db
.iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchange.baseUrl)
.toArray();
if (!coins || coins.length === 0) { if (!coins || coins.length === 0) {
continue; continue;
} }

View File

@ -412,7 +412,8 @@ async function refreshReveal(
coinSource: { coinSource: {
type: CoinSourceType.Refresh, type: CoinSourceType.Refresh,
oldCoinPub: refreshSession.meltCoinPub, oldCoinPub: refreshSession.meltCoinPub,
} },
suspended: false,
}; };
coins.push(coin); coins.push(coin);

View File

@ -240,6 +240,7 @@ async function processPlanchet(
reservePub: planchet.reservePub, reservePub: planchet.reservePub,
withdrawSessionId: withdrawalSessionId, withdrawSessionId: withdrawalSessionId,
}, },
suspended: false,
}; };
let withdrawSessionFinished = false; let withdrawSessionFinished = false;

View File

@ -674,11 +674,9 @@ export interface CoinRecord {
exchangeBaseUrl: string; exchangeBaseUrl: string;
/** /**
* We have withdrawn the coin, but it's not accepted by the exchange anymore. * The coin is currently suspended, and will not be used for payments.
* We have to tell an auditor and wait for compensation or for the exchange
* to fix it.
*/ */
suspended?: boolean; suspended: boolean;
/** /**
* Blinding key used when withdrawing the coin. * Blinding key used when withdrawing the coin.

View File

@ -759,6 +759,20 @@ export class WithdrawResponse {
ev_sig: string; ev_sig: string;
} }
export interface CoinDumpJson {
coins: Array<{
denom_pub: string;
denom_pub_hash: string;
denom_value: string;
coin_pub: string;
exchange_base_url: string;
remaining_value: string;
refresh_parent_coin_pub: string | undefined;
withdrawal_reserve_pub: string | undefined;
coin_suspended: boolean;
}>;
}
export type AmountString = string; export type AmountString = string;
export type Base32String = string; export type Base32String = string;
export type EddsaSignatureString = string; export type EddsaSignatureString = string;

View File

@ -19,6 +19,12 @@ export class Logger {
info(message: string, ...args: any[]) { info(message: string, ...args: any[]) {
console.log(`${new Date().toISOString()} ${this.tag} INFO ` + message, ...args); console.log(`${new Date().toISOString()} ${this.tag} INFO ` + message, ...args);
} }
warn(message: string, ...args: any[]) {
console.log(`${new Date().toISOString()} ${this.tag} WARN ` + message, ...args);
}
error(message: string, ...args: any[]) {
console.log(`${new Date().toISOString()} ${this.tag} ERROR ` + message, ...args);
}
trace(message: any, ...args: any[]) { trace(message: any, ...args: any[]) {
console.log(`${new Date().toISOString()} ${this.tag} TRACE ` + message, ...args) console.log(`${new Date().toISOString()} ${this.tag} TRACE ` + message, ...args)
} }

View File

@ -53,8 +53,9 @@ import {
ReserveRecord, ReserveRecord,
Stores, Stores,
ReserveRecordStatus, ReserveRecordStatus,
CoinSourceType,
} from "./types/dbTypes"; } from "./types/dbTypes";
import { MerchantRefundPermission } from "./types/talerTypes"; import { MerchantRefundPermission, CoinDumpJson } from "./types/talerTypes";
import { import {
BenchmarkResult, BenchmarkResult,
ConfirmPayResult, ConfirmPayResult,
@ -238,7 +239,10 @@ export class Wallet {
await this.processOnePendingOperation(p, forceNow); await this.processOnePendingOperation(p, forceNow);
} catch (e) { } catch (e) {
if (e instanceof OperationFailedAndReportedError) { if (e instanceof OperationFailedAndReportedError) {
console.error("Operation failed:", JSON.stringify(e.operationError, undefined, 2)); console.error(
"Operation failed:",
JSON.stringify(e.operationError, undefined, 2),
);
} else { } else {
console.error(e); console.error(e);
} }
@ -254,7 +258,7 @@ export class Wallet {
public async runUntilDone(): Promise<void> { public async runUntilDone(): Promise<void> {
const p = new Promise((resolve, reject) => { const p = new Promise((resolve, reject) => {
// Run this asynchronously // Run this asynchronously
this.addNotificationListener(n => { this.addNotificationListener((n) => {
if ( if (
n.type === NotificationType.WaitingForRetry && n.type === NotificationType.WaitingForRetry &&
n.numGivingLiveness == 0 n.numGivingLiveness == 0
@ -263,7 +267,7 @@ export class Wallet {
resolve(); resolve();
} }
}); });
this.runRetryLoop().catch(e => { this.runRetryLoop().catch((e) => {
console.log("exception in wallet retry loop"); console.log("exception in wallet retry loop");
reject(e); reject(e);
}); });
@ -279,7 +283,7 @@ export class Wallet {
public async runUntilDoneAndStop(): Promise<void> { public async runUntilDoneAndStop(): Promise<void> {
const p = new Promise((resolve, reject) => { const p = new Promise((resolve, reject) => {
// Run this asynchronously // Run this asynchronously
this.addNotificationListener(n => { this.addNotificationListener((n) => {
if ( if (
n.type === NotificationType.WaitingForRetry && n.type === NotificationType.WaitingForRetry &&
n.numGivingLiveness == 0 n.numGivingLiveness == 0
@ -288,7 +292,7 @@ export class Wallet {
this.stop(); this.stop();
} }
}); });
this.runRetryLoop().catch(e => { this.runRetryLoop().catch((e) => {
console.log("exception in wallet retry loop"); console.log("exception in wallet retry loop");
reject(e); reject(e);
}); });
@ -371,9 +375,9 @@ export class Wallet {
async fillDefaults() { async fillDefaults() {
await this.db.runWithWriteTransaction( await this.db.runWithWriteTransaction(
[Stores.config, Stores.currencies], [Stores.config, Stores.currencies],
async tx => { async (tx) => {
let applied = false; let applied = false;
await tx.iter(Stores.config).forEach(x => { await tx.iter(Stores.config).forEach((x) => {
if (x.key == "currencyDefaultsApplied" && x.value == true) { if (x.key == "currencyDefaultsApplied" && x.value == true) {
applied = true; applied = true;
} }
@ -506,7 +510,7 @@ export class Wallet {
try { try {
const refreshGroupId = await this.db.runWithWriteTransaction( const refreshGroupId = await this.db.runWithWriteTransaction(
[Stores.refreshGroups], [Stores.refreshGroups],
async tx => { async (tx) => {
return await createRefreshGroup( return await createRefreshGroup(
tx, tx,
[{ coinPub: oldCoinPub }], [{ coinPub: oldCoinPub }],
@ -573,13 +577,13 @@ export class Wallet {
async getReserves(exchangeBaseUrl: string): Promise<ReserveRecord[]> { async getReserves(exchangeBaseUrl: string): Promise<ReserveRecord[]> {
return await this.db return await this.db
.iter(Stores.reserves) .iter(Stores.reserves)
.filter(r => r.exchangeBaseUrl === exchangeBaseUrl); .filter((r) => r.exchangeBaseUrl === exchangeBaseUrl);
} }
async getCoinsForExchange(exchangeBaseUrl: string): Promise<CoinRecord[]> { async getCoinsForExchange(exchangeBaseUrl: string): Promise<CoinRecord[]> {
return await this.db return await this.db
.iter(Stores.coins) .iter(Stores.coins)
.filter(c => c.exchangeBaseUrl === exchangeBaseUrl); .filter((c) => c.exchangeBaseUrl === exchangeBaseUrl);
} }
async getCoins(): Promise<CoinRecord[]> { async getCoins(): Promise<CoinRecord[]> {
@ -598,22 +602,22 @@ export class Wallet {
async getSenderWireInfos(): Promise<SenderWireInfos> { async getSenderWireInfos(): Promise<SenderWireInfos> {
const m: { [url: string]: Set<string> } = {}; const m: { [url: string]: Set<string> } = {};
await this.db.iter(Stores.exchanges).forEach(x => { await this.db.iter(Stores.exchanges).forEach((x) => {
const wi = x.wireInfo; const wi = x.wireInfo;
if (!wi) { if (!wi) {
return; return;
} }
const s = (m[x.baseUrl] = m[x.baseUrl] || new Set()); const s = (m[x.baseUrl] = m[x.baseUrl] || new Set());
Object.keys(wi.feesForType).map(k => s.add(k)); Object.keys(wi.feesForType).map((k) => s.add(k));
}); });
const exchangeWireTypes: { [url: string]: string[] } = {}; const exchangeWireTypes: { [url: string]: string[] } = {};
Object.keys(m).map(e => { Object.keys(m).map((e) => {
exchangeWireTypes[e] = Array.from(m[e]); exchangeWireTypes[e] = Array.from(m[e]);
}); });
const senderWiresSet: Set<string> = new Set(); const senderWiresSet: Set<string> = new Set();
await this.db.iter(Stores.senderWires).forEach(x => { await this.db.iter(Stores.senderWires).forEach((x) => {
senderWiresSet.add(x.paytoUri); senderWiresSet.add(x.paytoUri);
}); });
@ -735,20 +739,20 @@ export class Wallet {
} }
const refundsDoneAmounts = Object.values( const refundsDoneAmounts = Object.values(
purchase.refundState.refundsDone, purchase.refundState.refundsDone,
).map(x => Amounts.parseOrThrow(x.perm.refund_amount)); ).map((x) => Amounts.parseOrThrow(x.perm.refund_amount));
const refundsPendingAmounts = Object.values( const refundsPendingAmounts = Object.values(
purchase.refundState.refundsPending, purchase.refundState.refundsPending,
).map(x => Amounts.parseOrThrow(x.perm.refund_amount)); ).map((x) => Amounts.parseOrThrow(x.perm.refund_amount));
const totalRefundAmount = Amounts.sum([ const totalRefundAmount = Amounts.sum([
...refundsDoneAmounts, ...refundsDoneAmounts,
...refundsPendingAmounts, ...refundsPendingAmounts,
]).amount; ]).amount;
const refundsDoneFees = Object.values( const refundsDoneFees = Object.values(
purchase.refundState.refundsDone, purchase.refundState.refundsDone,
).map(x => Amounts.parseOrThrow(x.perm.refund_amount)); ).map((x) => Amounts.parseOrThrow(x.perm.refund_amount));
const refundsPendingFees = Object.values( const refundsPendingFees = Object.values(
purchase.refundState.refundsPending, purchase.refundState.refundsPending,
).map(x => Amounts.parseOrThrow(x.perm.refund_amount)); ).map((x) => Amounts.parseOrThrow(x.perm.refund_amount));
const totalRefundFees = Amounts.sum([ const totalRefundFees = Amounts.sum([
...refundsDoneFees, ...refundsDoneFees,
...refundsPendingFees, ...refundsPendingFees,
@ -765,4 +769,65 @@ export class Wallet {
benchmarkCrypto(repetitions: number): Promise<BenchmarkResult> { benchmarkCrypto(repetitions: number): Promise<BenchmarkResult> {
return this.ws.cryptoApi.benchmark(repetitions); return this.ws.cryptoApi.benchmark(repetitions);
} }
async setCoinSuspended(coinPub: string, suspended: boolean): Promise<void> {
await this.db.runWithWriteTransaction([Stores.coins], async (tx) => {
const c = await tx.get(Stores.coins, coinPub);
if (!c) {
logger.warn(`coin ${coinPub} not found, won't suspend`);
return;
}
c.suspended = suspended;
await tx.put(Stores.coins, c);
});
}
/**
* Dump the public information of coins we have in an easy-to-process format.
*/
async dumpCoins(): Promise<CoinDumpJson> {
const coins = await this.db.iter(Stores.coins).toArray();
const coinsJson: CoinDumpJson = { coins: [] };
for (const c of coins) {
const denom = await this.db.get(Stores.denominations, [
c.exchangeBaseUrl,
c.denomPub,
]);
if (!denom) {
console.error("no denom session found for coin");
continue;
}
const cs = c.coinSource;
let refreshParentCoinPub: string | undefined;
if (cs.type == CoinSourceType.Refresh) {
refreshParentCoinPub = cs.oldCoinPub;
}
let withdrawalReservePub: string | undefined;
if (cs.type == CoinSourceType.Withdraw) {
const ws = await this.db.get(
Stores.withdrawalSession,
cs.withdrawSessionId,
);
if (!ws) {
console.error("no withdrawal session found for coin");
continue;
}
if (ws.source.type == "reserve") {
withdrawalReservePub = ws.source.reservePub;
}
}
coinsJson.coins.push({
coin_pub: c.coinPub,
denom_pub: c.denomPub,
denom_pub_hash: c.denomPubHash,
denom_value: Amounts.toString(denom.value),
exchange_base_url: c.exchangeBaseUrl,
refresh_parent_coin_pub: refreshParentCoinPub,
remaining_value: Amounts.toString(c.currentAmount),
withdrawal_reserve_pub: withdrawalReservePub,
coin_suspended: c.suspended,
});
}
return coinsJson;
}
} }