automatic refresh

This commit is contained in:
Florian Dold 2016-10-17 15:58:36 +02:00
parent 6262af4ad7
commit 8c0c4b5331
10 changed files with 207 additions and 43 deletions

View File

@ -282,7 +282,9 @@ namespace TalerNotify {
addHandler("taler-payment-failed", (msg: any, sendResponse: any) => { addHandler("taler-payment-failed", (msg: any, sendResponse: any) => {
const walletMsg = { const walletMsg = {
type: "payment-failed", type: "payment-failed",
detail: {}, detail: {
contractHash: msg.H_contract
},
}; };
chrome.runtime.sendMessage(walletMsg, (resp) => { chrome.runtime.sendMessage(walletMsg, (resp) => {
sendResponse(); sendResponse();
@ -290,8 +292,20 @@ namespace TalerNotify {
}); });
addHandler("taler-payment-succeeded", (msg: any, sendResponse: any) => { addHandler("taler-payment-succeeded", (msg: any, sendResponse: any) => {
if (!msg.H_contract) {
console.error("H_contract missing in taler-payment-succeeded");
return;
}
console.log("got taler-payment-succeeded"); console.log("got taler-payment-succeeded");
sendResponse(); const walletMsg = {
type: "payment-succeeded",
detail: {
contractHash: msg.H_contract,
},
};
chrome.runtime.sendMessage(walletMsg, (resp) => {
sendResponse();
})
}); });
addHandler("taler-get-payment", (msg: any, sendResponse: any) => { addHandler("taler-get-payment", (msg: any, sendResponse: any) => {

View File

@ -58,6 +58,9 @@ const paths = {
"pages/*.{ts,tsx}", "pages/*.{ts,tsx}",
"!**/*.d.ts", "!**/*.d.ts",
], ],
decl: [
"lib/refs.d.ts",
],
dev: [ dev: [
"test/tests/*.{ts,tsx}", "test/tests/*.{ts,tsx}",
], ],
@ -73,10 +76,10 @@ const paths = {
"lib/module-trampoline.js", "lib/module-trampoline.js",
"popup/**/*.{html,css}", "popup/**/*.{html,css}",
"pages/**/*.{html,css}", "pages/**/*.{html,css}",
"lib/**/*.d.ts",
"background/*.html", "background/*.html",
], ],
extra: [ extra: [
"lib/**/*.d.ts",
"AUTHORS", "AUTHORS",
"README", "README",
"COPYING", "COPYING",
@ -220,7 +223,12 @@ gulp.task("compile-prod", ["clean"], function () {
tsArgs.outDir = "."; tsArgs.outDir = ".";
// We don't want source maps for production // We don't want source maps for production
tsArgs.sourceMap = undefined; tsArgs.sourceMap = undefined;
return gulp.src(paths.ts.release, {base: "."}) let opts = {base: "."};
const files = concatStreams(
gulp.src(paths.ts.release, opts),
gulp.src(paths.ts.decl, opts));
return files
.pipe(ts(tsArgs)) .pipe(ts(tsArgs))
.pipe(gulp.dest("build/ext/")); .pipe(gulp.dest("build/ext/"));
}); });
@ -274,6 +282,7 @@ gulp.task("srcdist", [], function () {
// We can't just concat patterns due to exclude patterns // We can't just concat patterns due to exclude patterns
const files = concatStreams( const files = concatStreams(
gulp.src(paths.ts.release, opts), gulp.src(paths.ts.release, opts),
gulp.src(paths.ts.decl, opts),
gulp.src(paths.ts.dev, opts), gulp.src(paths.ts.dev, opts),
gulp.src(paths.dist, opts), gulp.src(paths.dist, opts),
gulp.src(paths.extra, opts)); gulp.src(paths.extra, opts));
@ -346,9 +355,13 @@ function tsconfig(confBase) {
// Generate the tsconfig file // Generate the tsconfig file
// that should be used during development. // that should be used during development.
gulp.task("tsconfig", function() { gulp.task("tsconfig", function() {
return gulp.src(Array.prototype.concat(paths.ts.release, paths.ts.dev), {base: "."}) let opts = {base: "."};
.pipe(tsconfig(tsBaseArgs)) const files = concatStreams(
.pipe(gulp.dest(".")); gulp.src(paths.ts.release, opts),
gulp.src(paths.ts.dev, opts),
gulp.src(paths.ts.decl, opts));
return files.pipe(tsconfig(tsBaseArgs))
.pipe(gulp.dest("."));
}); });

View File

@ -1,6 +0,0 @@
// Help the TypeScript compiler find declarations.
/// <reference path="decl/lib.es6.d.ts" />
/// <reference path="decl/urijs/URIjs.d.ts" />
/// <reference path="decl/systemjs/systemjs.d.ts" />

View File

@ -203,6 +203,8 @@ namespace RpcFunctions {
let newAmount = new native.Amount(cd.coin.currentAmount); let newAmount = new native.Amount(cd.coin.currentAmount);
newAmount.sub(coinSpend); newAmount.sub(coinSpend);
cd.coin.currentAmount = newAmount.toJson(); cd.coin.currentAmount = newAmount.toJson();
cd.coin.dirty = true;
cd.coin.transactionPending = true;
let d = new native.DepositRequestPS({ let d = new native.DepositRequestPS({
h_contract: native.HashCode.fromCrock(offer.H_contract), h_contract: native.HashCode.fromCrock(offer.H_contract),
@ -338,4 +340,8 @@ namespace RpcFunctions {
return refreshSession; return refreshSession;
} }
export function hashString(str: string): string {
const b = native.ByteArray.fromStringWithNull(str);
return b.hash().toCrock();
}
} }

View File

@ -25,7 +25,7 @@
*/ */
const DB_NAME = "taler"; const DB_NAME = "taler";
const DB_VERSION = 8; const DB_VERSION = 10;
/** /**
* Return a promise that resolves * Return a promise that resolves
@ -59,14 +59,15 @@ export function openTalerDb(): Promise<IDBDatabase> {
"contract.repurchase_correlation_id" "contract.repurchase_correlation_id"
]); ]);
db.createObjectStore("precoins", db.createObjectStore("precoins", {keyPath: "coinPub"});
{keyPath: "coinPub", autoIncrement: true});
const history = db.createObjectStore("history", const history = db.createObjectStore("history",
{ {
keyPath: "id", keyPath: "id",
autoIncrement: true autoIncrement: true
}); });
history.createIndex("timestamp", "timestamp"); history.createIndex("timestamp", "timestamp");
db.createObjectStore("refresh",
{keyPath: "meltCoinPub"});
break; break;
default: default:
if (e.oldVersion != DB_VERSION) { if (e.oldVersion != DB_VERSION) {

View File

@ -279,6 +279,19 @@ export interface Coin {
* to fix it. * to fix it.
*/ */
suspended?: boolean; 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;
} }

View File

@ -27,7 +27,7 @@ import {
IExchangeInfo, IExchangeInfo,
Denomination, Denomination,
Notifier, Notifier,
WireInfo, RefreshSession, ReserveRecord WireInfo, RefreshSession, ReserveRecord, CoinPaySig
} from "./types"; } from "./types";
import {HttpResponse, RequestException} from "./http"; import {HttpResponse, RequestException} from "./http";
import {QueryRoot} from "./query"; import {QueryRoot} from "./query";
@ -135,11 +135,22 @@ interface ExchangeCoins {
[exchangeUrl: string]: CoinWithDenom[]; [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 { interface Transaction {
contractHash: string; contractHash: string;
contract: Contract; contract: Contract;
payReq: any; payReq: PayReq;
merchantSig: string; merchantSig: string;
} }
@ -492,16 +503,17 @@ export class Wallet {
private async recordConfirmPay(offer: Offer, private async recordConfirmPay(offer: Offer,
payCoinInfo: PayCoinInfo, payCoinInfo: PayCoinInfo,
chosenExchange: string): Promise<void> { chosenExchange: string): Promise<void> {
let payReq: any = {}; let payReq: PayReq = {
payReq["amount"] = offer.contract.amount; amount: offer.contract.amount,
payReq["coins"] = payCoinInfo.map((x) => x.sig); coins: payCoinInfo.map((x) => x.sig),
payReq["H_contract"] = offer.H_contract; H_contract: offer.H_contract,
payReq["max_fee"] = offer.contract.max_fee; max_fee: offer.contract.max_fee,
payReq["merchant_sig"] = offer.merchant_sig; merchant_sig: offer.merchant_sig,
payReq["exchange"] = URI(chosenExchange).href(); exchange: URI(chosenExchange).href(),
payReq["refund_deadline"] = offer.contract.refund_deadline; refund_deadline: offer.contract.refund_deadline,
payReq["timestamp"] = offer.contract.timestamp; timestamp: offer.contract.timestamp,
payReq["transaction_id"] = offer.contract.transaction_id; transaction_id: offer.contract.transaction_id,
};
let t: Transaction = { let t: Transaction = {
contractHash: offer.H_contract, contractHash: offer.H_contract,
contract: offer.contract, contract: offer.contract,
@ -792,6 +804,8 @@ export class Wallet {
denomSig: denomSig, denomSig: denomSig,
currentAmount: pc.coinValue, currentAmount: pc.coinValue,
exchangeBaseUrl: pc.exchangeBaseUrl, exchangeBaseUrl: pc.exchangeBaseUrl,
dirty: false,
transactionPending: false,
}; };
return coin; return coin;
} }
@ -1127,18 +1141,53 @@ export class Wallet {
let newCoinDenoms = getWithdrawDenomList(availableAmount, let newCoinDenoms = getWithdrawDenomList(availableAmount,
availableDenoms); availableDenoms);
newCoinDenoms = [newCoinDenoms[0]];
console.log("refreshing into", newCoinDenoms); console.log("refreshing into", newCoinDenoms);
if (newCoinDenoms.length == 0) {
console.log("not refreshing, value too small");
return;
}
let refreshSession: RefreshSession = await ( let refreshSession: RefreshSession = await (
this.cryptoApi.createRefreshSession(exchange.baseUrl, this.cryptoApi.createRefreshSession(exchange.baseUrl,
3, 3,
coin, coin,
newCoinDenoms, newCoinDenoms,
oldDenom.fee_refresh)); oldDenom.fee_refresh));
let reqUrl = URI("refresh/melt").absoluteTo(exchange!.baseUrl); 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 r = await this.q().get<RefreshSession>("refresh", oldCoinPub);
if (!r) {
throw Error("refresh session does not exist anymore");
}
await this.refreshReveal(r);
}
async refreshMelt(refreshSession: RefreshSession): Promise<void> {
if (refreshSession.norevealIndex != undefined) {
console.error("won't melt again");
return;
}
let coin = await this.q().get<Coin>("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 = { let meltCoin = {
coin_pub: coin.coinPub, coin_pub: coin.coinPub,
denom_pub: coin.denomPub, denom_pub: coin.denomPub,
@ -1148,7 +1197,7 @@ export class Wallet {
}; };
let coinEvs = refreshSession.preCoinsForGammas.map((x) => x.map((y) => y.coinEv)); let coinEvs = refreshSession.preCoinsForGammas.map((x) => x.map((y) => y.coinEv));
let req = { let req = {
"new_denoms": newCoinDenoms.map((d) => d.denom_pub), "new_denoms": refreshSession.newDenoms,
"melt_coin": meltCoin, "melt_coin": meltCoin,
"transfer_pubs": refreshSession.transferPubs, "transfer_pubs": refreshSession.transferPubs,
"coin_evs": coinEvs, "coin_evs": coinEvs,
@ -1178,11 +1227,10 @@ export class Wallet {
refreshSession.norevealIndex = norevealIndex; refreshSession.norevealIndex = norevealIndex;
this.refreshReveal(refreshSession); await this.q().put("refresh", refreshSession).finish();
// FIXME: implement rest
} }
async refreshReveal(refreshSession: RefreshSession): Promise<void> { async refreshReveal(refreshSession: RefreshSession): Promise<void> {
let norevealIndex = refreshSession.norevealIndex; let norevealIndex = refreshSession.norevealIndex;
if (norevealIndex == undefined) { if (norevealIndex == undefined) {
@ -1196,12 +1244,54 @@ export class Wallet {
"transfer_privs": privs, "transfer_privs": privs,
}; };
let reqUrl = URI("refresh/reveal").absoluteTo(refreshSession.exchangeBaseUrl); let reqUrl = URI("refresh/reveal")
.absoluteTo(refreshSession.exchangeBaseUrl);
console.log("reveal request:", req); console.log("reveal request:", req);
let resp = await this.http.postJson(reqUrl, req); let resp = await this.http.postJson(reqUrl, req);
console.log("session:", refreshSession); console.log("session:", refreshSession);
console.log("reveal response:", resp); 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<IExchangeInfo>("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}; return {isRepurchase: false};
} }
} }
async paymentSucceeded(contractHash: string): Promise<any> {
const doPaymentSucceeded = async () => {
let t = await this.q().get<Transaction>("transactions", contractHash);
if (!t) {
console.error("contract not found");
return;
}
for (let pc of t.payReq.coins) {
let c = await this.q().get<Coin>("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;
}
} }

View File

@ -218,6 +218,13 @@ function makeHandlers(db: IDBDatabase,
wallet.updateExchanges(); wallet.updateExchanges();
return Promise.resolve(); return Promise.resolve();
}, },
["payment-succeeded"]: function (detail, sender) {
let contractHash = detail.contractHash;
if (!contractHash) {
return Promise.reject(Error("contractHash missing"));
}
return wallet.paymentSucceeded(contractHash);
},
}; };
} }

View File

@ -13,8 +13,10 @@
"noImplicitAny": true "noImplicitAny": true
}, },
"files": [ "files": [
"lib/components.ts",
"test/tests/taler.ts",
"lib/refs.d.ts",
"lib/i18n.ts", "lib/i18n.ts",
"lib/refs.ts",
"lib/shopApi.ts", "lib/shopApi.ts",
"lib/taler-wallet-lib.ts", "lib/taler-wallet-lib.ts",
"lib/wallet/checkable.ts", "lib/wallet/checkable.ts",
@ -38,7 +40,6 @@
"pages/show-db.ts", "pages/show-db.ts",
"pages/confirm-contract.tsx", "pages/confirm-contract.tsx",
"pages/confirm-create-reserve.tsx", "pages/confirm-create-reserve.tsx",
"pages/tree.tsx", "pages/tree.tsx"
"test/tests/taler.ts"
] ]
} }

@ -1 +1 @@
Subproject commit e9eb34dfb56c8e5a18fa7d555aa651a4532e8f3c Subproject commit 02271055cbe9e62cffb3713d109a77015df625d1