From b4815b2b0675960df9316c61be848387d6954a9b Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 28 Sep 2016 18:54:48 +0200 Subject: more async --- lib/wallet/wallet.ts | 482 ++++++++++++++++++++++++--------------------------- 1 file changed, 223 insertions(+), 259 deletions(-) (limited to 'lib/wallet/wallet.ts') diff --git a/lib/wallet/wallet.ts b/lib/wallet/wallet.ts index 951e0728a..987c46940 100644 --- a/lib/wallet/wallet.ts +++ b/lib/wallet/wallet.ts @@ -125,24 +125,11 @@ export class Offer { } -interface ConfirmPayRequest { - offer: Offer; -} - interface ExchangeCoins { [exchangeUrl: string]: CoinWithDenom[]; } -interface CoinPaySig { - coin_sig: string; - coin_pub: string; - ub_sig: string; - denom_pub: string; - f: AmountJson; -} - - interface Transaction { contractHash: string; contract: Contract; @@ -505,36 +492,34 @@ export class Wallet { * Add a contract to the wallet and sign coins, * but do not send them yet. */ - confirmPay(offer: Offer): Promise { + async confirmPay(offer: Offer): Promise { console.log("executing confirmPay"); - return Query(this.db) - .get("transactions", offer.H_contract) - .then((transaction) => { - if (transaction) { - // Already payed ... - return {}; - } - return Promise.resolve().then(() => { - return this.getPossibleExchangeCoins(offer.contract.amount, - offer.contract.max_fee, - offer.contract.exchanges) - }).then((mcs) => { - if (Object.keys(mcs).length == 0) { - console.log("not confirming payment, insufficient coins"); - return { - error: "coins-insufficient", - }; - } - let exchangeUrl = Object.keys(mcs)[0]; + let transaction = await Query(this.db) + .get("transactions", offer.H_contract); - return this.cryptoApi.signDeposit(offer, mcs[exchangeUrl]) - .then((ds) => this.recordConfirmPay(offer, - ds, - exchangeUrl)) - .then(() => ({})); - }); - }); + if (transaction) { + // Already payed ... + return {}; + } + + let mcs = await this.getPossibleExchangeCoins(offer.contract.amount, + offer.contract.max_fee, + offer.contract.exchanges); + + if (Object.keys(mcs).length == 0) { + console.log("not confirming payment, insufficient coins"); + return { + error: "coins-insufficient", + }; + } + let exchangeUrl = Object.keys(mcs)[0]; + + let ds = await this.cryptoApi.signDeposit(offer, mcs[exchangeUrl]); + await this.recordConfirmPay(offer, + ds, + exchangeUrl); + return {}; } @@ -542,33 +527,27 @@ export class Wallet { * Add a contract to the wallet and sign coins, * but do not send them yet. */ - checkPay(offer: Offer): Promise { - console.log("executing checkPay"); - + async checkPay(offer: Offer): Promise { // First check if we already payed for it. - return Query(this.db) - .get("transactions", offer.H_contract) - .then((transaction) => { - if (transaction) { - return {isPayed: true}; - } + let transaction = await + Query(this.db) + .get("transactions", offer.H_contract); + if (transaction) { + return {isPayed: true}; + } - // If not already payed, check if we could pay for it. - return Promise.resolve().then(() => { - return this.getPossibleExchangeCoins(offer.contract.amount, - offer.contract.max_fee, - offer.contract.exchanges) - }).then((mcs) => { - if (Object.keys(mcs).length == 0) { - console.log("not confirming payment, insufficient coins"); - return { - error: "coins-insufficient", - }; - } - return {isPayed: false}; - }); + // 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); - }); + if (Object.keys(mcs).length == 0) { + console.log("not confirming payment, insufficient coins"); + return { + error: "coins-insufficient", + }; + } + return {isPayed: false}; } @@ -576,25 +555,21 @@ export class Wallet { * Retrieve all necessary information for looking up the contract * with the given hash. */ - executePayment(H_contract: string): Promise { - return Promise.resolve().then(() => { - return Query(this.db) - .get("transactions", H_contract) - .then((t) => { - if (!t) { - return { - success: false, - contractFound: false, - } - } - let resp = { - success: true, - payReq: t.payReq, - contract: t.contract, - }; - return resp; - }); - }); + async executePayment(H_contract: string): Promise { + let t = await Query(this.db) + .get("transactions", H_contract); + if (!t) { + return { + success: false, + contractFound: false, + } + } + let resp = { + success: true, + payReq: t.payReq, + contract: t.contract, + }; + return resp; } @@ -602,95 +577,92 @@ export class Wallet { * First fetch information requred to withdraw from the reserve, * then deplete the reserve, withdrawing coins until it is empty. */ - private processReserve(reserveRecord: any, retryDelayMs: number = 250): void { + private async processReserve(reserveRecord: any, + retryDelayMs: number = 250): Promise { const opId = "reserve-" + reserveRecord.reserve_pub; this.startOperation(opId); - this.updateExchangeFromUrl(reserveRecord.exchange_base_url) - .then((exchange) => - this.updateReserve(reserveRecord.reserve_pub, exchange) - .then((reserve) => this.depleteReserve(reserve, - exchange))) - .then(() => { - this.stopOperation(opId); - let depleted = { - type: "depleted-reserve", - timestamp: (new Date).getTime(), - detail: { - reservePub: reserveRecord.reserve_pub, - } - }; - return Query(this.db).put("history", depleted).finish(); - }) - .catch((e) => { - // Don't show progress while we're sleeping - this.stopOperation(opId); - // random, exponential backoff truncated at 3 minutes - let nextDelay = Math.min(2 * retryDelayMs + retryDelayMs * Math.random(), - 3000 * 60); - console.warn(`Failed to deplete reserve, trying again in ${retryDelayMs} ms`); - setTimeout(() => this.processReserve(reserveRecord, nextDelay), - retryDelayMs); - }); + + try { + let exchange = await this.updateExchangeFromUrl(reserveRecord.exchange_base_url); + let reserve = await this.updateReserve(reserveRecord.reserve_pub, + exchange); + await this.depleteReserve(reserve, exchange); + let depleted = { + type: "depleted-reserve", + timestamp: (new Date).getTime(), + detail: { + reservePub: reserveRecord.reserve_pub, + } + }; + await Query(this.db).put("history", depleted).finish(); + } catch (e) { + // random, exponential backoff truncated at 3 minutes + let nextDelay = Math.min(2 * retryDelayMs + retryDelayMs * Math.random(), + 3000 * 60); + console.warn(`Failed to deplete reserve, trying again in ${retryDelayMs} ms`); + setTimeout(() => this.processReserve(reserveRecord, nextDelay), + retryDelayMs); + } finally { + this.stopOperation(opId); + } } - private processPreCoin(preCoin: any, retryDelayMs = 100): void { - this.withdrawExecute(preCoin) - .then((c) => this.storeCoin(c)) - .catch((e) => { - console.error("Failed to withdraw coin from precoin, retrying in", - retryDelayMs, - "ms"); - console.error(e); - // exponential backoff truncated at one minute - let nextRetryDelayMs = Math.min(retryDelayMs * 2, 1000 * 60); - setTimeout(() => this.processPreCoin(preCoin, nextRetryDelayMs), - retryDelayMs); - }); + private async processPreCoin(preCoin: any, + retryDelayMs = 100): Promise { + 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); + // exponential backoff truncated at one minute + let nextRetryDelayMs = Math.min(retryDelayMs * 2, 1000 * 60); + setTimeout(() => this.processPreCoin(preCoin, nextRetryDelayMs), + retryDelayMs); + } } /** * Create a reserve, but do not flag it as confirmed yet. */ - createReserve(req: CreateReserveRequest): Promise { - return this.cryptoApi.createEddsaKeypair().then((keypair) => { - const now = (new Date).getTime(); - const canonExchange = canonicalizeBaseUrl(req.exchange); - - const reserveRecord = { - reserve_pub: keypair.pub, - reserve_priv: keypair.priv, - exchange_base_url: canonExchange, - created: now, - last_query: null, - current_amount: null, - requested_amount: req.amount, - confirmed: false, - }; + async createReserve(req: CreateReserveRequest): Promise { + let keypair = await this.cryptoApi.createEddsaKeypair(); + const now = (new Date).getTime(); + const canonExchange = canonicalizeBaseUrl(req.exchange); + + const reserveRecord = { + reserve_pub: keypair.pub, + reserve_priv: keypair.priv, + exchange_base_url: canonExchange, + created: now, + last_query: null, + current_amount: null, + requested_amount: req.amount, + confirmed: false, + }; + const historyEntry = { + type: "create-reserve", + timestamp: now, + detail: { + requestedAmount: req.amount, + reservePub: reserveRecord.reserve_pub, + } + }; - const historyEntry = { - type: "create-reserve", - timestamp: now, - detail: { - requestedAmount: req.amount, - reservePub: reserveRecord.reserve_pub, - } - }; + await Query(this.db) + .put("reserves", reserveRecord) + .put("history", historyEntry) + .finish(); - return Query(this.db) - .put("reserves", reserveRecord) - .put("history", historyEntry) - .finish() - .then(() => { - let r: CreateReserveResponse = { - exchange: canonExchange, - reservePub: keypair.pub, - }; - return r; - }); - }); + let r: CreateReserveResponse = { + exchange: canonExchange, + reservePub: keypair.pub, + }; + return r; } @@ -703,7 +675,7 @@ export class Wallet { * A confirmed reserve should be shown to the user in the UI, while * an unconfirmed reserve should be hidden. */ - confirmReserve(req: ConfirmReserveRequest): Promise { + async confirmReserve(req: ConfirmReserveRequest): Promise { const now = (new Date).getTime(); const historyEntry = { type: "confirm-reserve", @@ -712,63 +684,59 @@ export class Wallet { reservePub: req.reservePub, } }; + let r = await Query(this.db) + .get("reserves", req.reservePub); + if (!r) { + console.error("Unable to confirm reserve, not found in DB"); + return; + } + r.confirmed = true; return Query(this.db) - .get("reserves", req.reservePub) - .then((r) => { - if (!r) { - console.error("Unable to confirm reserve, not found in DB"); - return; - } - r.confirmed = true; - return Query(this.db) - .put("reserves", r) - .put("history", historyEntry) - .finish() - .then(() => { - // Do this in the background - this.processReserve(r); - }); + .put("reserves", r) + .put("history", historyEntry) + .finish() + .then(() => { + // Do this in the background + this.processReserve(r); }); } - private withdrawExecute(pc: PreCoin): Promise { - return Query(this.db) - .get("reserves", pc.reservePub) - .then((r) => { - let wd: any = {}; - wd.denom_pub = pc.denomPub; - wd.reserve_pub = pc.reservePub; - wd.reserve_sig = pc.withdrawSig; - wd.coin_ev = pc.coinEv; - let reqUrl = URI("reserve/withdraw").absoluteTo(r.exchange_base_url); - return this.http.postJson(reqUrl, wd); - }) - .then(resp => { - if (resp.status != 200) { - throw new RequestException({ - hint: "Withdrawal failed", - status: resp.status - }); - } - let r = JSON.parse(resp.responseText); - return this.cryptoApi.rsaUnblind(r.ev_sig, pc.blindingKey, pc.denomPub) - .then((denomSig) => { - let coin: Coin = { - coinPub: pc.coinPub, - coinPriv: pc.coinPriv, - denomPub: pc.denomPub, - denomSig: denomSig, - currentAmount: pc.coinValue, - exchangeBaseUrl: pc.exchangeBaseUrl, - }; - return coin; - - }); + private async withdrawExecute(pc: PreCoin): Promise { + let reserve = await Query(this.db) + .get("reserves", pc.reservePub); + + let wd: any = {}; + wd.denom_pub = pc.denomPub; + wd.reserve_pub = pc.reservePub; + wd.reserve_sig = pc.withdrawSig; + wd.coin_ev = pc.coinEv; + let reqUrl = URI("reserve/withdraw").absoluteTo(reserve.exchange_base_url); + let resp = await this.http.postJson(reqUrl, wd); + + + if (resp.status != 200) { + throw new RequestException({ + hint: "Withdrawal failed", + status: resp.status }); + } + let r = JSON.parse(resp.responseText); + let denomSig = await this.cryptoApi.rsaUnblind(r.ev_sig, + pc.blindingKey, + pc.denomPub); + let coin: Coin = { + coinPub: pc.coinPub, + coinPriv: pc.coinPriv, + denomPub: pc.denomPub, + denomSig: denomSig, + currentAmount: pc.coinValue, + exchangeBaseUrl: pc.exchangeBaseUrl, + }; + return coin; } - storeCoin(coin: Coin): Promise { + async storeCoin(coin: Coin): Promise { console.log("storing coin", new Date()); let historyEntry = { type: "withdraw", @@ -777,14 +745,12 @@ export class Wallet { coinPub: coin.coinPub, } }; - return Query(this.db) + await Query(this.db) .delete("precoins", coin.coinPub) .add("coins", coin) .add("history", historyEntry) - .finish() - .then(() => { - this.notifier.notify(); - }); + .finish(); + this.notifier.notify(); } @@ -823,8 +789,8 @@ export class Wallet { * Update the information about a reserve that is stored in the wallet * by quering the reserve's exchange. */ - private updateReserve(reservePub: string, - exchange: IExchangeInfo): Promise { + private async updateReserve(reservePub: string, + exchange: IExchangeInfo): Promise { return Query(this.db) .get("reserves", reservePub) .then((reserve) => { @@ -862,48 +828,47 @@ export class Wallet { /** * Get the wire information for the exchange with the given base URL. */ - getWireInfo(exchangeBaseUrl: string): Promise { + async getWireInfo(exchangeBaseUrl: string): Promise { exchangeBaseUrl = canonicalizeBaseUrl(exchangeBaseUrl); let reqUrl = URI("wire").absoluteTo(exchangeBaseUrl); - return this.http.get(reqUrl).then((resp: HttpResponse) => { - if (resp.status != 200) { - throw Error("/wire request failed"); - } + let resp = await this.http.get(reqUrl); - let wiJson = JSON.parse(resp.responseText); - if (!wiJson) { - throw Error("/wire response malformed") - } - return wiJson; - }); + if (resp.status != 200) { + throw Error("/wire request failed"); + } + + let wiJson = JSON.parse(resp.responseText); + if (!wiJson) { + throw Error("/wire response malformed") + } + return wiJson; } - getReserveCreationInfo(baseUrl: string, - amount: AmountJson): Promise { - let p = this.updateExchangeFromUrl(baseUrl); - return p.then((exchangeInfo: IExchangeInfo) => { - let selectedDenoms = getWithdrawDenomList(amount, - 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) - .reduce((a, b) => Amounts.add(a, b).amount); - return this.getWireInfo(baseUrl).then((wireInfo) => { - let ret: ReserveCreationInfo = { - exchangeInfo, - selectedDenoms, - wireInfo, - withdrawFee: acc, - overhead: Amounts.sub(amount, actualCoinCost).amount, - }; - return ret; - }); + async getReserveCreationInfo(baseUrl: string, + amount: AmountJson): Promise { + let exchangeInfo = await this.updateExchangeFromUrl(baseUrl); - }); + let selectedDenoms = getWithdrawDenomList(amount, + 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) + .reduce((a, b) => Amounts.add(a, b).amount); + + let wireInfo = await this.getWireInfo(baseUrl); + + let ret: ReserveCreationInfo = { + exchangeInfo, + selectedDenoms, + wireInfo, + withdrawFee: acc, + overhead: Amounts.sub(amount, actualCoinCost).amount, + }; + return ret; } @@ -912,16 +877,15 @@ export class Wallet { * Optionally link the reserve entry to the new or existing * exchange entry in then DB. */ - updateExchangeFromUrl(baseUrl: string): Promise { + async updateExchangeFromUrl(baseUrl: string): Promise { baseUrl = canonicalizeBaseUrl(baseUrl); let reqUrl = URI("keys").absoluteTo(baseUrl); - return this.http.get(reqUrl).then((resp) => { - if (resp.status != 200) { - throw Error("/keys request failed"); - } - let exchangeKeysJson = KeysJson.checked(JSON.parse(resp.responseText)); - return this.updateExchangeFromJson(baseUrl, exchangeKeysJson); - }); + let resp = await this.http.get(reqUrl); + if (resp.status != 200) { + throw Error("/keys request failed"); + } + let exchangeKeysJson = KeysJson.checked(JSON.parse(resp.responseText)); + return this.updateExchangeFromJson(baseUrl, exchangeKeysJson); } private async suspendCoins(exchangeInfo: IExchangeInfo): Promise { -- cgit v1.2.3