diff options
author | Florian Dold <florian.dold@gmail.com> | 2016-10-12 02:55:53 +0200 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2016-10-12 02:55:53 +0200 |
commit | d4be3906e32ac7d9933c6030d6493f2f2152bdd9 (patch) | |
tree | 3341586381b6975a3c1fa99ca69c63fba5ac9c35 /lib/wallet/wallet.ts | |
parent | dbcd85451edfc33f2e0a42c431f0cf3ab3b12876 (diff) | |
download | wallet-core-d4be3906e32ac7d9933c6030d6493f2f2152bdd9.tar.xz |
tree view of wallet db
Diffstat (limited to 'lib/wallet/wallet.ts')
-rw-r--r-- | lib/wallet/wallet.ts | 253 |
1 files changed, 141 insertions, 112 deletions
diff --git a/lib/wallet/wallet.ts b/lib/wallet/wallet.ts index 67288f666..337ed8255 100644 --- a/lib/wallet/wallet.ts +++ b/lib/wallet/wallet.ts @@ -29,19 +29,19 @@ import { Notifier, WireInfo } from "./types"; -import {HttpResponse, RequestException} from "./http"; -import {Query} from "./query"; -import {Checkable} from "./checkable"; -import {canonicalizeBaseUrl} from "./helpers"; -import {ReserveCreationInfo, Amounts} from "./types"; -import {PreCoin} from "./types"; -import {Reserve} from "./types"; -import {CryptoApi} from "./cryptoApi"; -import {Coin} from "./types"; -import {PayCoinInfo} from "./types"; -import {CheckRepurchaseResult} from "./types"; -import {Contract} from "./types"; -import {ExchangeHandle} from "./types"; +import { HttpResponse, RequestException } from "./http"; +import { Query } from "./query"; +import { Checkable } from "./checkable"; +import { canonicalizeBaseUrl } from "./helpers"; +import { ReserveCreationInfo, Amounts } from "./types"; +import { PreCoin } from "./types"; +import { Reserve } from "./types"; +import { CryptoApi } from "./cryptoApi"; +import { Coin } from "./types"; +import { PayCoinInfo } from "./types"; +import { CheckRepurchaseResult } from "./types"; +import { Contract } from "./types"; +import { ExchangeHandle } from "./types"; "use strict"; @@ -55,11 +55,11 @@ interface ReserveRecord { reserve_priv: string, exchange_base_url: string, created: number, - last_query: number|null, + last_query: number | null, /** * Current amount left in the reserve */ - current_amount: AmountJson|null, + current_amount: AmountJson | null, /** * Amount requested when the reserve was created. * When a reserve is re-used (rare!) the current_amount can @@ -229,7 +229,7 @@ function flatMap<T, U>(xs: T[], f: (x: T) => U[]): U[] { } -function getTalerStampSec(stamp: string): number|null { +function getTalerStampSec(stamp: string): number | null { const m = stamp.match(/\/?Date\(([0-9]*)\)\/?/); if (!m) { return null; @@ -256,14 +256,14 @@ function isWithdrawableDenom(d: Denomination) { interface HttpRequestLibrary { req(method: string, - url: string|uri.URI, - options?: any): Promise<HttpResponse>; + url: string | uri.URI, + options?: any): Promise<HttpResponse>; - get(url: string|uri.URI): Promise<HttpResponse>; + get(url: string | uri.URI): Promise<HttpResponse>; - postJson(url: string|uri.URI, body: any): Promise<HttpResponse>; + postJson(url: string | uri.URI, body: any): Promise<HttpResponse>; - postForm(url: string|uri.URI, form: any): Promise<HttpResponse>; + postForm(url: string | uri.URI, form: any): Promise<HttpResponse>; } @@ -288,7 +288,7 @@ interface KeyUpdateInfo { * amount, but never larger. */ function getWithdrawDenomList(amountAvailable: AmountJson, - denoms: Denomination[]): Denomination[] { + denoms: Denomination[]): Denomination[] { let remaining = Amounts.copy(amountAvailable); const ds: Denomination[] = []; @@ -331,9 +331,9 @@ export class Wallet { private runningOperations: Set<string> = new Set(); constructor(db: IDBDatabase, - http: HttpRequestLibrary, - badge: Badge, - notifier: Notifier) { + http: HttpRequestLibrary, + badge: Badge, + notifier: Notifier) { this.db = db; this.http = http; this.badge = badge; @@ -363,9 +363,9 @@ export class Wallet { .iter("exchanges") .reduce((exchange: IExchangeInfo) => { this.updateExchangeFromUrl(exchange.baseUrl) - .catch((e) => { - console.error("updating exchange failed", e); - }); + .catch((e) => { + console.error("updating exchange failed", e); + }); }); } @@ -397,8 +397,8 @@ export class Wallet { * but only if the sum the coins' remaining value exceeds the payment amount. */ private async getPossibleExchangeCoins(paymentAmount: AmountJson, - depositFeeLimit: AmountJson, - allowedExchanges: ExchangeHandle[]): Promise<ExchangeCoins> { + depositFeeLimit: AmountJson, + allowedExchanges: ExchangeHandle[]): Promise<ExchangeCoins> { // Mapping from exchange base URL to list of coins together with their // denomination let m: ExchangeCoins = {}; @@ -411,9 +411,9 @@ export class Wallet { let coin: Coin = mc[1]; if (coin.suspended) { console.log("skipping suspended coin", - coin.denomPub, - "from exchange", - exchange.baseUrl); + coin.denomPub, + "from exchange", + exchange.baseUrl); return; } let denom = exchange.active_denoms.find((e) => e.denom_pub === coin.denomPub); @@ -425,7 +425,7 @@ export class Wallet { console.warn("same pubkey for different currencies"); return; } - let cd = {coin, denom}; + let cd = { coin, denom }; let x = m[url]; if (!x) { m[url] = [cd]; @@ -446,7 +446,7 @@ export class Wallet { console.log("Checking for merchant's exchange", JSON.stringify(info)); return [ Query(this.db) - .iter("exchanges", {indexName: "pubKey", only: info.master_pub}) + .iter("exchanges", { indexName: "pubKey", only: info.master_pub }) .indexJoin("coins", "exchangeBaseUrl", (exchange) => exchange.baseUrl) .reduce((x) => storeExchangeCoin(x, info.url)) ]; @@ -467,38 +467,38 @@ export class Wallet { // under depositFeeLimit nextExchange: - for (let key in m) { - let coins = m[key]; - // Sort by ascending deposit fee - coins.sort((o1, o2) => Amounts.cmp(o1.denom.fee_deposit, - o2.denom.fee_deposit)); - let maxFee = Amounts.copy(depositFeeLimit); - let minAmount = Amounts.copy(paymentAmount); - let accFee = Amounts.copy(coins[0].denom.fee_deposit); - let accAmount = Amounts.getZero(coins[0].coin.currentAmount.currency); - let usableCoins: CoinWithDenom[] = []; - nextCoin: - for (let i = 0; i < coins.length; i++) { - let coinAmount = Amounts.copy(coins[i].coin.currentAmount); - let coinFee = coins[i].denom.fee_deposit; - if (Amounts.cmp(coinAmount, coinFee) <= 0) { - continue nextCoin; - } - accFee = Amounts.add(accFee, coinFee).amount; - accAmount = Amounts.add(accAmount, coinAmount).amount; - if (Amounts.cmp(accFee, maxFee) >= 0) { - // FIXME: if the fees are too high, we have - // to cover them ourselves .... - console.log("too much fees"); - continue nextExchange; - } - usableCoins.push(coins[i]); - if (Amounts.cmp(accAmount, minAmount) >= 0) { - ret[key] = usableCoins; - continue nextExchange; - } - } + for (let key in m) { + let coins = m[key]; + // Sort by ascending deposit fee + coins.sort((o1, o2) => Amounts.cmp(o1.denom.fee_deposit, + o2.denom.fee_deposit)); + let maxFee = Amounts.copy(depositFeeLimit); + let minAmount = Amounts.copy(paymentAmount); + let accFee = Amounts.copy(coins[0].denom.fee_deposit); + let accAmount = Amounts.getZero(coins[0].coin.currentAmount.currency); + let usableCoins: CoinWithDenom[] = []; + nextCoin: + for (let i = 0; i < coins.length; i++) { + let coinAmount = Amounts.copy(coins[i].coin.currentAmount); + let coinFee = coins[i].denom.fee_deposit; + if (Amounts.cmp(coinAmount, coinFee) <= 0) { + continue nextCoin; + } + accFee = Amounts.add(accFee, coinFee).amount; + accAmount = Amounts.add(accAmount, coinAmount).amount; + if (Amounts.cmp(accFee, maxFee) >= 0) { + // FIXME: if the fees are too high, we have + // to cover them ourselves .... + console.log("too much fees"); + continue nextExchange; + } + usableCoins.push(coins[i]); + if (Amounts.cmp(accAmount, minAmount) >= 0) { + ret[key] = usableCoins; + continue nextExchange; + } } + } return ret; } @@ -508,8 +508,8 @@ export class Wallet { * pay for a contract in the wallet's database. */ private async recordConfirmPay(offer: Offer, - payCoinInfo: PayCoinInfo, - chosenExchange: string): Promise<void> { + payCoinInfo: PayCoinInfo, + chosenExchange: string): Promise<void> { let payReq: any = {}; payReq["amount"] = offer.contract.amount; payReq["coins"] = payCoinInfo.map((x) => x.sig); @@ -571,8 +571,8 @@ export class Wallet { } let mcs = await this.getPossibleExchangeCoins(offer.contract.amount, - offer.contract.max_fee, - offer.contract.exchanges); + offer.contract.max_fee, + offer.contract.exchanges); if (Object.keys(mcs).length == 0) { console.log("not confirming payment, insufficient coins"); @@ -584,8 +584,8 @@ export class Wallet { let ds = await this.cryptoApi.signDeposit(offer, mcs[exchangeUrl]); await this.recordConfirmPay(offer, - ds, - exchangeUrl); + ds, + exchangeUrl); return {}; } @@ -600,13 +600,13 @@ export class Wallet { Query(this.db) .get("transactions", offer.H_contract); if (transaction) { - return {isPayed: true}; + return { isPayed: true }; } // If not already payed, check if we could pay for it. let mcs = await this.getPossibleExchangeCoins(offer.contract.amount, - offer.contract.max_fee, - offer.contract.exchanges); + offer.contract.max_fee, + offer.contract.exchanges); if (Object.keys(mcs).length == 0) { console.log("not confirming payment, insufficient coins"); @@ -614,7 +614,7 @@ export class Wallet { error: "coins-insufficient", }; } - return {isPayed: false}; + return { isPayed: false }; } @@ -645,14 +645,14 @@ export class Wallet { * then deplete the reserve, withdrawing coins until it is empty. */ private async processReserve(reserveRecord: ReserveRecord, - retryDelayMs: number = 250): Promise<void> { + retryDelayMs: number = 250): Promise<void> { const opId = "reserve-" + reserveRecord.reserve_pub; this.startOperation(opId); try { let exchange = await this.updateExchangeFromUrl(reserveRecord.exchange_base_url); let reserve = await this.updateReserve(reserveRecord.reserve_pub, - exchange); + exchange); let n = await this.depleteReserve(reserve, exchange); if (n != 0) { @@ -672,10 +672,10 @@ export class Wallet { } catch (e) { // random, exponential backoff truncated at 3 minutes let nextDelay = Math.min(2 * retryDelayMs + retryDelayMs * Math.random(), - 3000 * 60); + 3000 * 60); console.warn(`Failed to deplete reserve, trying again in ${retryDelayMs} ms`); setTimeout(() => this.processReserve(reserveRecord, nextDelay), - retryDelayMs); + retryDelayMs); } finally { this.stopOperation(opId); } @@ -683,18 +683,18 @@ export class Wallet { private async processPreCoin(preCoin: PreCoin, - retryDelayMs = 100): Promise<void> { + retryDelayMs = 100): Promise<void> { try { const coin = await this.withdrawExecute(preCoin); this.storeCoin(coin); } catch (e) { console.error("Failed to withdraw coin from precoin, retrying in", - retryDelayMs, - "ms", e); + retryDelayMs, + "ms", e); // exponential backoff truncated at one minute let nextRetryDelayMs = Math.min(retryDelayMs * 2, 1000 * 60); setTimeout(() => this.processPreCoin(preCoin, nextRetryDelayMs), - retryDelayMs); + retryDelayMs); } } @@ -801,8 +801,8 @@ export class Wallet { } let r = JSON.parse(resp.responseText); let denomSig = await this.cryptoApi.rsaUnblind(r.ev_sig, - pc.blindingKey, - pc.denomPub); + pc.blindingKey, + pc.denomPub); let coin: Coin = { coinPub: pc.coinPub, coinPriv: pc.coinPriv, @@ -840,7 +840,7 @@ export class Wallet { private async withdraw(denom: Denomination, reserve: Reserve): Promise<void> { console.log("creating pre coin at", new Date()); let preCoin = await this.cryptoApi - .createPreCoin(denom, reserve); + .createPreCoin(denom, reserve); await Query(this.db) .put("precoins", preCoin) .finish(); @@ -852,10 +852,10 @@ export class Wallet { * Withdraw coins from a reserve until it is empty. */ private async depleteReserve(reserve: any, - exchange: IExchangeInfo): Promise<number> { + exchange: IExchangeInfo): Promise<number> { let denomsAvailable: Denomination[] = copy(exchange.active_denoms); let denomsForWithdraw = getWithdrawDenomList(reserve.current_amount, - denomsAvailable); + denomsAvailable); let ps = denomsForWithdraw.map((denom) => this.withdraw(denom, reserve)); await Promise.all(ps); @@ -868,11 +868,11 @@ export class Wallet { * by quering the reserve's exchange. */ private async updateReserve(reservePub: string, - exchange: IExchangeInfo): Promise<Reserve> { + exchange: IExchangeInfo): Promise<Reserve> { let reserve = await Query(this.db) .get("reserves", reservePub); let reqUrl = URI("reserve/status").absoluteTo(exchange.baseUrl); - reqUrl.query({'reserve_pub': reservePub}); + reqUrl.query({ 'reserve_pub': reservePub }); let resp = await this.http.get(reqUrl); if (resp.status != 200) { throw Error(); @@ -922,18 +922,18 @@ export class Wallet { } async getReserveCreationInfo(baseUrl: string, - amount: AmountJson): Promise<ReserveCreationInfo> { + amount: AmountJson): Promise<ReserveCreationInfo> { let exchangeInfo = await this.updateExchangeFromUrl(baseUrl); let selectedDenoms = getWithdrawDenomList(amount, - exchangeInfo.active_denoms); + exchangeInfo.active_denoms); let acc = Amounts.getZero(amount.currency); for (let d of selectedDenoms) { acc = Amounts.add(acc, d.fee_withdraw).amount; } let actualCoinCost = selectedDenoms .map((d: Denomination) => Amounts.add(d.value, - d.fee_withdraw).amount) + d.fee_withdraw).amount) .reduce((a, b) => Amounts.add(a, b).amount); let wireInfo = await this.getWireInfo(baseUrl); @@ -968,7 +968,7 @@ export class Wallet { private async suspendCoins(exchangeInfo: IExchangeInfo): Promise<void> { let suspendedCoins = await Query(this.db) .iter("coins", - {indexName: "exchangeBaseUrl", only: exchangeInfo.baseUrl}) + { indexName: "exchangeBaseUrl", only: exchangeInfo.baseUrl }) .reduce((coin: Coin, suspendedCoins: Coin[]) => { if (!exchangeInfo.active_denoms.find((c) => c.denom_pub == coin.denomPub)) { return Array.prototype.concat(suspendedCoins, [coin]); @@ -987,7 +987,7 @@ export class Wallet { private async updateExchangeFromJson(baseUrl: string, - exchangeKeysJson: KeysJson): Promise<IExchangeInfo> { + exchangeKeysJson: KeysJson): Promise<IExchangeInfo> { const updateTimeSec = getTalerStampSec(exchangeKeysJson.list_issue_date); if (updateTimeSec === null) { throw Error("invalid update time"); @@ -1016,7 +1016,7 @@ export class Wallet { } let updatedExchangeInfo = await this.updateExchangeInfo(exchangeInfo, - exchangeKeysJson); + exchangeKeysJson); await this.suspendCoins(updatedExchangeInfo); await Query(this.db) @@ -1028,7 +1028,7 @@ export class Wallet { private async updateExchangeInfo(exchangeInfo: IExchangeInfo, - newKeys: KeysJson): Promise<IExchangeInfo> { + newKeys: KeysJson): Promise<IExchangeInfo> { if (exchangeInfo.masterPublicKey != newKeys.master_public_key) { throw Error("public keys do not match"); } @@ -1064,15 +1064,15 @@ export class Wallet { return true; }); - let ps = denomsToCheck.map(async(denom) => { + let ps = denomsToCheck.map(async (denom) => { let valid = await this.cryptoApi - .isValidDenom(denom, - exchangeInfo.masterPublicKey); + .isValidDenom(denom, + exchangeInfo.masterPublicKey); if (!valid) { console.error("invalid denomination", - denom, - "with key", - exchangeInfo.masterPublicKey); + denom, + "with key", + exchangeInfo.masterPublicKey); // FIXME: report to auditors } exchangeInfo.active_denoms.push(denom); @@ -1099,7 +1099,7 @@ export class Wallet { acc = Amounts.getZero(c.currentAmount.currency); } byCurrency[c.currentAmount.currency] = Amounts.add(c.currentAmount, - acc).amount; + acc).amount; return byCurrency; } @@ -1107,7 +1107,7 @@ export class Wallet { .iter("coins") .reduce(collectBalances, {}); - return {balances: byCurrency}; + return { balances: byCurrency }; } @@ -1122,12 +1122,41 @@ export class Wallet { let history = await Query(this.db) - .iter("history", {indexName: "timestamp"}) + .iter("history", { indexName: "timestamp" }) .reduce(collect, []); - return {history}; + return { history }; + } + + async getExchanges(): Promise<IExchangeInfo[]> { + return Query(this.db) + .iter<IExchangeInfo>("exchanges") + .flatMap((e) => [e]) + .toArray(); } + async getReserves(exchangeBaseUrl: string): Promise<Reserve[]> { + return Query(this.db) + .iter<Reserve>("reserves") + .filter((r: Reserve) => r.exchange_base_url === exchangeBaseUrl) + .toArray(); + } + + async getCoins(exchangeBaseUrl: string): Promise<Coin[]> { + return Query(this.db) + .iter<Coin>("coins") + .filter((c: Coin) => c.exchangeBaseUrl === exchangeBaseUrl) + .toArray(); + } + + async getPreCoins(exchangeBaseUrl: string): Promise<PreCoin[]> { + return Query(this.db) + .iter<PreCoin>("precoins") + .filter((c: PreCoin) => c.exchangeBaseUrl === exchangeBaseUrl) + .toArray(); + } + + async hashContract(contract: any): Promise<string> { return this.cryptoApi.hashString(canonicalJson(contract)); } @@ -1138,12 +1167,12 @@ export class Wallet { async checkRepurchase(contract: Contract): Promise<CheckRepurchaseResult> { if (!contract.repurchase_correlation_id) { console.log("no repurchase: no correlation id"); - return {isRepurchase: false}; + return { isRepurchase: false }; } let result: Transaction = await Query(this.db) .getIndexed("transactions", - "repurchase", - [contract.merchant_pub, contract.repurchase_correlation_id]); + "repurchase", + [contract.merchant_pub, contract.repurchase_correlation_id]); if (result) { console.assert(result.contract.repurchase_correlation_id == contract.repurchase_correlation_id); @@ -1153,7 +1182,7 @@ export class Wallet { existingFulfillmentUrl: result.contract.fulfillment_url, }; } else { - return {isRepurchase: false}; + return { isRepurchase: false }; } } } |