aboutsummaryrefslogtreecommitdiff
path: root/lib/wallet
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2016-10-17 15:58:36 +0200
committerFlorian Dold <florian.dold@gmail.com>2016-10-17 15:58:36 +0200
commit8c0c4b5331d3cb467bb24c253fae3ca623ba5205 (patch)
tree755247f130c627a093e6e01894d103bceef18312 /lib/wallet
parent6262af4ad73763e736bfb96a88c5d66e0ec51532 (diff)
downloadwallet-core-8c0c4b5331d3cb467bb24c253fae3ca623ba5205.tar.xz
automatic refresh
Diffstat (limited to 'lib/wallet')
-rw-r--r--lib/wallet/cryptoLib.ts6
-rw-r--r--lib/wallet/db.ts7
-rw-r--r--lib/wallet/types.ts13
-rw-r--r--lib/wallet/wallet.ts161
-rw-r--r--lib/wallet/wxMessaging.ts7
5 files changed, 168 insertions, 26 deletions
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<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) {
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<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));
+
+ 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<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;
+ }
}
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);
+ },
};
}