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) => {
const walletMsg = {
type: "payment-failed",
detail: {},
detail: {
contractHash: msg.H_contract
},
};
chrome.runtime.sendMessage(walletMsg, (resp) => {
sendResponse();
@ -290,8 +292,20 @@ namespace TalerNotify {
});
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");
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) => {

View File

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

View File

@ -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<IDBDatabase> {
"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) {

View File

@ -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;
}

View File

@ -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<void> {
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));
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 = {
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<void> {
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<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};
}
}
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();
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
},
"files": [
"lib/components.ts",
"test/tests/taler.ts",
"lib/refs.d.ts",
"lib/i18n.ts",
"lib/refs.ts",
"lib/shopApi.ts",
"lib/taler-wallet-lib.ts",
"lib/wallet/checkable.ts",
@ -38,7 +40,6 @@
"pages/show-db.ts",
"pages/confirm-contract.tsx",
"pages/confirm-create-reserve.tsx",
"pages/tree.tsx",
"test/tests/taler.ts"
"pages/tree.tsx"
]
}

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