From 8c0c4b5331d3cb467bb24c253fae3ca623ba5205 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 17 Oct 2016 15:58:36 +0200 Subject: automatic refresh --- lib/refs.ts | 6 -- lib/wallet/cryptoLib.ts | 6 ++ lib/wallet/db.ts | 7 +- lib/wallet/types.ts | 13 ++++ lib/wallet/wallet.ts | 161 +++++++++++++++++++++++++++++++++++++++------- lib/wallet/wxMessaging.ts | 7 ++ 6 files changed, 168 insertions(+), 32 deletions(-) delete mode 100644 lib/refs.ts (limited to 'lib') diff --git a/lib/refs.ts b/lib/refs.ts deleted file mode 100644 index a9c2c5eb8..000000000 --- a/lib/refs.ts +++ /dev/null @@ -1,6 +0,0 @@ - -// Help the TypeScript compiler find declarations. - -/// -/// -/// diff --git a/lib/wallet/cryptoLib.ts b/lib/wallet/cryptoLib.ts index 3b9d6d228..2782327ac 100644 --- a/lib/wallet/cryptoLib.ts +++ b/lib/wallet/cryptoLib.ts @@ -203,6 +203,8 @@ namespace RpcFunctions { let newAmount = new native.Amount(cd.coin.currentAmount); newAmount.sub(coinSpend); cd.coin.currentAmount = newAmount.toJson(); + cd.coin.dirty = true; + cd.coin.transactionPending = true; let d = new native.DepositRequestPS({ h_contract: native.HashCode.fromCrock(offer.H_contract), @@ -338,4 +340,8 @@ namespace RpcFunctions { return refreshSession; } + export function hashString(str: string): string { + const b = native.ByteArray.fromStringWithNull(str); + return b.hash().toCrock(); + } } \ No newline at end of file diff --git a/lib/wallet/db.ts b/lib/wallet/db.ts index 55e943393..9133330a2 100644 --- a/lib/wallet/db.ts +++ b/lib/wallet/db.ts @@ -25,7 +25,7 @@ */ const DB_NAME = "taler"; -const DB_VERSION = 8; +const DB_VERSION = 10; /** * Return a promise that resolves @@ -59,14 +59,15 @@ export function openTalerDb(): Promise { "contract.repurchase_correlation_id" ]); - db.createObjectStore("precoins", - {keyPath: "coinPub", autoIncrement: true}); + db.createObjectStore("precoins", {keyPath: "coinPub"}); const history = db.createObjectStore("history", { keyPath: "id", autoIncrement: true }); history.createIndex("timestamp", "timestamp"); + db.createObjectStore("refresh", + {keyPath: "meltCoinPub"}); break; default: if (e.oldVersion != DB_VERSION) { diff --git a/lib/wallet/types.ts b/lib/wallet/types.ts index f1b1eedce..9d634618a 100644 --- a/lib/wallet/types.ts +++ b/lib/wallet/types.ts @@ -279,6 +279,19 @@ export interface Coin { * to fix it. */ suspended?: boolean; + + /** + * Was the coin revealed in a transaction? + */ + dirty: boolean; + + /** + * Is the coin currently involved in a transaction? + * + * This delays refreshing until the transaction is finished or + * aborted. + */ + transactionPending: boolean; } diff --git a/lib/wallet/wallet.ts b/lib/wallet/wallet.ts index 43f4227dd..2c5210607 100644 --- a/lib/wallet/wallet.ts +++ b/lib/wallet/wallet.ts @@ -27,7 +27,7 @@ import { IExchangeInfo, Denomination, Notifier, - WireInfo, RefreshSession, ReserveRecord + WireInfo, RefreshSession, ReserveRecord, CoinPaySig } from "./types"; import {HttpResponse, RequestException} from "./http"; import {QueryRoot} from "./query"; @@ -135,11 +135,22 @@ interface ExchangeCoins { [exchangeUrl: string]: CoinWithDenom[]; } +interface PayReq { + amount: AmountJson; + coins: CoinPaySig[]; + H_contract: string; + max_fee: AmountJson; + merchant_sig: string; + exchange: string; + refund_deadline: string; + timestamp: string; + transaction_id: number; +} interface Transaction { contractHash: string; contract: Contract; - payReq: any; + payReq: PayReq; merchantSig: string; } @@ -492,16 +503,17 @@ export class Wallet { private async recordConfirmPay(offer: Offer, payCoinInfo: PayCoinInfo, chosenExchange: string): Promise { - let payReq: any = {}; - payReq["amount"] = offer.contract.amount; - payReq["coins"] = payCoinInfo.map((x) => x.sig); - payReq["H_contract"] = offer.H_contract; - payReq["max_fee"] = offer.contract.max_fee; - payReq["merchant_sig"] = offer.merchant_sig; - payReq["exchange"] = URI(chosenExchange).href(); - payReq["refund_deadline"] = offer.contract.refund_deadline; - payReq["timestamp"] = offer.contract.timestamp; - payReq["transaction_id"] = offer.contract.transaction_id; + let payReq: PayReq = { + amount: offer.contract.amount, + coins: payCoinInfo.map((x) => x.sig), + H_contract: offer.H_contract, + max_fee: offer.contract.max_fee, + merchant_sig: offer.merchant_sig, + exchange: URI(chosenExchange).href(), + refund_deadline: offer.contract.refund_deadline, + timestamp: offer.contract.timestamp, + transaction_id: offer.contract.transaction_id, + }; let t: Transaction = { contractHash: offer.H_contract, contract: offer.contract, @@ -792,6 +804,8 @@ export class Wallet { denomSig: denomSig, currentAmount: pc.coinValue, exchangeBaseUrl: pc.exchangeBaseUrl, + dirty: false, + transactionPending: false, }; return coin; } @@ -1127,18 +1141,53 @@ export class Wallet { let newCoinDenoms = getWithdrawDenomList(availableAmount, availableDenoms); - newCoinDenoms = [newCoinDenoms[0]]; console.log("refreshing into", newCoinDenoms); + if (newCoinDenoms.length == 0) { + console.log("not refreshing, value too small"); + return; + } + let refreshSession: RefreshSession = await ( this.cryptoApi.createRefreshSession(exchange.baseUrl, - 3, - coin, - newCoinDenoms, - oldDenom.fee_refresh)); + 3, + coin, + newCoinDenoms, + oldDenom.fee_refresh)); + + coin.currentAmount = Amounts.sub(coin.currentAmount, + refreshSession.valueWithFee).amount; + + // FIXME: we should check whether the amount still matches! + await this.q() + .put("refresh", refreshSession) + .put("coins", coin) + .finish(); + + await this.refreshMelt(refreshSession); - let reqUrl = URI("refresh/melt").absoluteTo(exchange!.baseUrl); + let r = await this.q().get("refresh", oldCoinPub); + if (!r) { + throw Error("refresh session does not exist anymore"); + } + await this.refreshReveal(r); + } + + + async refreshMelt(refreshSession: RefreshSession): Promise { + if (refreshSession.norevealIndex != undefined) { + console.error("won't melt again"); + return; + } + + let coin = await this.q().get("coins", refreshSession.meltCoinPub); + if (!coin) { + console.error("can't melt coin, it does not exist"); + return; + } + + let reqUrl = URI("refresh/melt").absoluteTo(refreshSession.exchangeBaseUrl); let meltCoin = { coin_pub: coin.coinPub, denom_pub: coin.denomPub, @@ -1148,7 +1197,7 @@ export class Wallet { }; let coinEvs = refreshSession.preCoinsForGammas.map((x) => x.map((y) => y.coinEv)); let req = { - "new_denoms": newCoinDenoms.map((d) => d.denom_pub), + "new_denoms": refreshSession.newDenoms, "melt_coin": meltCoin, "transfer_pubs": refreshSession.transferPubs, "coin_evs": coinEvs, @@ -1178,11 +1227,10 @@ export class Wallet { refreshSession.norevealIndex = norevealIndex; - this.refreshReveal(refreshSession); - - // FIXME: implement rest + await this.q().put("refresh", refreshSession).finish(); } + async refreshReveal(refreshSession: RefreshSession): Promise { let norevealIndex = refreshSession.norevealIndex; if (norevealIndex == undefined) { @@ -1196,12 +1244,54 @@ export class Wallet { "transfer_privs": privs, }; - let reqUrl = URI("refresh/reveal").absoluteTo(refreshSession.exchangeBaseUrl); + let reqUrl = URI("refresh/reveal") + .absoluteTo(refreshSession.exchangeBaseUrl); console.log("reveal request:", req); let resp = await this.http.postJson(reqUrl, req); console.log("session:", refreshSession); console.log("reveal response:", resp); + + if (resp.status != 200) { + console.log("error: /refresh/reveal returned status " + resp.status); + return; + } + + let respJson = JSON.parse(resp.responseText); + + if (!respJson.ev_sigs || !Array.isArray(respJson.ev_sigs)) { + console.log("/refresh/reveal did not contain ev_sigs"); + } + + let exchange = await this.q().get("exchanges", refreshSession.exchangeBaseUrl); + if (!exchange) { + console.error(`exchange ${refreshSession.exchangeBaseUrl} not found`); + return; + } + + for (let i = 0; i < respJson.ev_sigs.length; i++) { + let denom = exchange.all_denoms.find((d) => d.denom_pub == refreshSession.newDenoms[i]); + if (!denom) { + console.error("denom not found"); + continue; + } + let pc = refreshSession.preCoinsForGammas[refreshSession.norevealIndex!][i]; + let denomSig = await this.cryptoApi.rsaUnblind(respJson.ev_sigs[i].ev_sig, + pc.blindingKey, + denom.denom_pub); + let coin: Coin = { + coinPub: pc.publicKey, + coinPriv: pc.privateKey, + denomPub: denom.denom_pub, + denomSig: denomSig, + currentAmount: denom.value, + exchangeBaseUrl: refreshSession.exchangeBaseUrl, + dirty: false, + transactionPending: false, + }; + + await this.q().put("coins", coin).finish(); + } } @@ -1283,4 +1373,29 @@ export class Wallet { return {isRepurchase: false}; } } + + + async paymentSucceeded(contractHash: string): Promise { + const doPaymentSucceeded = async () => { + let t = await this.q().get("transactions", contractHash); + if (!t) { + console.error("contract not found"); + return; + } + for (let pc of t.payReq.coins) { + let c = await this.q().get("coins", pc.coin_pub); + if (!c) { + console.error("coin not found"); + return; + } + c.transactionPending = false; + await this.q().put("coins", c).finish(); + } + for (let c of t.payReq.coins) { + this.refresh(c.coin_pub); + } + }; + doPaymentSucceeded(); + return; + } } diff --git a/lib/wallet/wxMessaging.ts b/lib/wallet/wxMessaging.ts index 07f5cc1d8..1c3876772 100644 --- a/lib/wallet/wxMessaging.ts +++ b/lib/wallet/wxMessaging.ts @@ -218,6 +218,13 @@ function makeHandlers(db: IDBDatabase, wallet.updateExchanges(); return Promise.resolve(); }, + ["payment-succeeded"]: function (detail, sender) { + let contractHash = detail.contractHash; + if (!contractHash) { + return Promise.reject(Error("contractHash missing")); + } + return wallet.paymentSucceeded(contractHash); + }, }; } -- cgit v1.2.3