aboutsummaryrefslogtreecommitdiff
path: root/extension
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2016-02-11 18:17:02 +0100
committerFlorian Dold <florian.dold@gmail.com>2016-02-11 18:17:02 +0100
commitc34a6612ec079a78e67325dac8d9a18775abe022 (patch)
tree5480ab85a56a64fa4fc192490df215483fd64619 /extension
parent164d5f20c3dbb60c8898fb4aa7d729ec8d9b7537 (diff)
refactoring in preparation for #4121
Diffstat (limited to 'extension')
-rw-r--r--extension/lib/wallet/wallet.ts248
1 files changed, 137 insertions, 111 deletions
diff --git a/extension/lib/wallet/wallet.ts b/extension/lib/wallet/wallet.ts
index f94e9c87e..f5db93c91 100644
--- a/extension/lib/wallet/wallet.ts
+++ b/extension/lib/wallet/wallet.ts
@@ -167,7 +167,7 @@ export class Contract {
@Checkable.Class
-export class Offer {
+export class Offer {
@Checkable.Value(Contract)
contract: Contract;
@@ -259,6 +259,10 @@ function copy(o) {
}
+/**
+ * Rank two denomination by how desireable it is to withdraw them,
+ * based on their fees and value.
+ */
function rankDenom(denom1: any, denom2: any) {
// Slow ... we should find a better way than to convert it evert time.
let v1 = new native.Amount(denom1.value);
@@ -267,17 +271,112 @@ function rankDenom(denom1: any, denom2: any) {
}
+/**
+ * Create a pre-coin of the given denomination to be withdrawn from then given
+ * reserve.
+ */
+function createPreCoin(denom: Denomination, reserve: Reserve): PreCoin {
+ let reservePriv = new native.EddsaPrivateKey();
+ reservePriv.loadCrock(reserve.reserve_priv);
+ let reservePub = new native.EddsaPublicKey();
+ reservePub.loadCrock(reserve.reserve_pub);
+ let denomPub = native.RsaPublicKey.fromCrock(denom.denom_pub);
+ let coinPriv = native.EddsaPrivateKey.create();
+ let coinPub = coinPriv.getPublicKey();
+ let blindingFactor = native.RsaBlindingKey.create(1024);
+ let pubHash: native.HashCode = coinPub.hash();
+ let ev: native.ByteArray = native.rsaBlind(pubHash,
+ blindingFactor,
+ denomPub);
+
+ if (!denom.fee_withdraw) {
+ throw Error("Field fee_withdraw missing");
+ }
+
+ let amountWithFee = new native.Amount(denom.value);
+ amountWithFee.add(new native.Amount(denom.fee_withdraw));
+ let withdrawFee = new native.Amount(denom.fee_withdraw);
+
+ // Signature
+ let withdrawRequest = new native.WithdrawRequestPS({
+ reserve_pub: reservePub,
+ amount_with_fee: amountWithFee.toNbo(),
+ withdraw_fee: withdrawFee.toNbo(),
+ h_denomination_pub: denomPub.encode().hash(),
+ h_coin_envelope: ev.hash()
+ });
+
+ var sig = native.eddsaSign(withdrawRequest.toPurpose(), reservePriv);
+
+ let preCoin: PreCoin = {
+ reservePub: reservePub.toCrock(),
+ blindingKey: blindingFactor.toCrock(),
+ coinPub: coinPub.toCrock(),
+ coinPriv: coinPriv.toCrock(),
+ denomPub: denomPub.encode().toCrock(),
+ mintBaseUrl: reserve.mint_base_url,
+ withdrawSig: sig.toCrock(),
+ coinEv: ev.toCrock(),
+ coinValue: denom.value
+ };
+ return preCoin;
+}
+
+
+/**
+ * Get a list of denominations (with repetitions possible)
+ * whose total value is as close as possible to the available
+ * amount, but never larger.
+ */
+function getWithdrawDenomList(amountAvailable: AmountJson,
+ denoms: Denomination[]): Denomination[] {
+ let remaining = new native.Amount(amountAvailable);
+ let ds: Denomination[] = [];
+
+ denoms = denoms.filter(isWithdrawableDenom);
+ denoms.sort(rankDenom);
+
+ // This is an arbitrary number of coins
+ // we can withdraw in one go. It's not clear if this limit
+ // is useful ...
+ for (let i = 0; i < 1000; i++) {
+ let found = false;
+ for (let d of denoms) {
+ let cost = new native.Amount(d.value);
+ cost.add(new native.Amount(d.fee_withdraw));
+ if (remaining.cmp(cost) < 0) {
+ continue;
+ }
+ found = true;
+ remaining.sub(cost);
+ ds.push(d);
+ }
+ if (!found) {
+ console.log("did not find coins for remaining ", remaining.toJson());
+ break;
+ }
+ }
+ return ds;
+}
+
+
export class Wallet {
private db: IDBDatabase;
private http: HttpRequestLibrary;
private badge: Badge;
+
constructor(db: IDBDatabase, http: HttpRequestLibrary, badge: Badge) {
this.db = db;
this.http = http;
this.badge = badge;
}
+
+ /**
+ * Generate updated coins (to store in the database)
+ * and deposit permissions for each given coin.
+ */
private static signDeposit(offer: Offer,
cds: CoinWithDenom[]): PayCoinInfo {
let ret = [];
@@ -304,7 +403,7 @@ export class Wallet {
newAmount.sub(coinSpend);
cd.coin.currentAmount = newAmount.toJson();
- let args: native.DepositRequestPS_Args = {
+ let d = new native.DepositRequestPS({
h_contract: native.HashCode.fromCrock(offer.H_contract),
h_wire: native.HashCode.fromCrock(offer.contract.H_wire),
amount_with_fee: coinSpend.toNbo(),
@@ -314,9 +413,7 @@ export class Wallet {
refund_deadline: native.AbsoluteTimeNbo.fromTalerString(offer.contract.refund_deadline),
timestamp: native.AbsoluteTimeNbo.fromTalerString(offer.contract.timestamp),
transaction_id: native.UInt64.fromNumber(offer.contract.transaction_id),
- };
-
- let d = new native.DepositRequestPS(args);
+ });
let coinSig = native.eddsaSign(d.toPurpose(),
native.EddsaPrivateKey.fromCrock(cd.coin.coinPriv))
@@ -338,15 +435,10 @@ export class Wallet {
/**
* Get mints and associated coins that are still spendable,
* but only if the sum the coins' remaining value exceeds the payment amount.
- * @param paymentAmount
- * @param depositFeeLimit
- * @param allowedMints
*/
private getPossibleMintCoins(paymentAmount: AmountJson,
depositFeeLimit: AmountJson,
allowedMints: MintInfo[]): Promise<MintCoins> {
-
-
let m: MintCoins = {};
function storeMintCoin(mc) {
@@ -562,7 +654,7 @@ export class Wallet {
reservePub: reserveRecord.reserve_pub,
}
};
-
+
return Query(this.db)
.put("reserves", reserveRecord)
.put("history", historyEntry)
@@ -611,56 +703,6 @@ export class Wallet {
}
- private withdrawPrepare(denom: Denomination,
- reserve: Reserve): Promise<PreCoin> {
- let reservePriv = new native.EddsaPrivateKey();
- reservePriv.loadCrock(reserve.reserve_priv);
- let reservePub = new native.EddsaPublicKey();
- reservePub.loadCrock(reserve.reserve_pub);
- let denomPub = native.RsaPublicKey.fromCrock(denom.denom_pub);
- let coinPriv = native.EddsaPrivateKey.create();
- let coinPub = coinPriv.getPublicKey();
- let blindingFactor = native.RsaBlindingKey.create(1024);
- let pubHash: native.HashCode = coinPub.hash();
- let ev: native.ByteArray = native.rsaBlind(pubHash,
- blindingFactor,
- denomPub);
-
- if (!denom.fee_withdraw) {
- throw Error("Field fee_withdraw missing");
- }
-
- let amountWithFee = new native.Amount(denom.value);
- amountWithFee.add(new native.Amount(denom.fee_withdraw));
- let withdrawFee = new native.Amount(denom.fee_withdraw);
-
- // Signature
- let withdrawRequest = new native.WithdrawRequestPS({
- reserve_pub: reservePub,
- amount_with_fee: amountWithFee.toNbo(),
- withdraw_fee: withdrawFee.toNbo(),
- h_denomination_pub: denomPub.encode().hash(),
- h_coin_envelope: ev.hash()
- });
-
- var sig = native.eddsaSign(withdrawRequest.toPurpose(), reservePriv);
-
- let preCoin: PreCoin = {
- reservePub: reservePub.toCrock(),
- blindingKey: blindingFactor.toCrock(),
- coinPub: coinPub.toCrock(),
- coinPriv: coinPriv.toCrock(),
- denomPub: denomPub.encode().toCrock(),
- mintBaseUrl: reserve.mint_base_url,
- withdrawSig: sig.toCrock(),
- coinEv: ev.toCrock(),
- coinValue: denom.value
- };
-
- return Query(this.db).put("precoins", preCoin).finish().then(() => preCoin);
- }
-
-
private withdrawExecute(pc: PreCoin): Promise<Coin> {
return Query(this.db)
.get("reserves", pc.reservePub)
@@ -736,10 +778,16 @@ export class Wallet {
}
- private withdraw(denom, reserve): Promise<void> {
- return this.withdrawPrepare(denom, reserve)
- .then((pc) => this.withdrawExecute(pc))
- .then((c) => this.storeCoin(c));
+ /**
+ * Withdraw one coins of the given denomination from the given reserve.
+ */
+ private withdraw(denom: Denomination, reserve: Reserve): Promise<void> {
+ let preCoin = createPreCoin(denom, reserve);
+ return Query(this.db)
+ .put("precoins", preCoin)
+ .finish()
+ .then(() => this.withdrawExecute(preCoin))
+ .then((c) => this.storeCoin(c));
}
@@ -747,55 +795,26 @@ export class Wallet {
* Withdraw coins from a reserve until it is empty.
*/
private depleteReserve(reserve, mint: Mint): Promise<void> {
- let denoms: Denomination[] = copy(mint.keys.denoms);
- let remaining = new native.Amount(reserve.current_amount);
-
- denoms = denoms.filter(isWithdrawableDenom);
-
- denoms.sort(rankDenom);
- let workList = [];
- for (let i = 0; i < 1000; i++) {
- let found = false;
- for (let d of denoms) {
- let cost = new native.Amount(d.value);
- cost.add(new native.Amount(d.fee_withdraw));
- if (remaining.cmp(cost) < 0) {
- continue;
- }
- found = true;
- remaining.sub(cost);
- workList.push(d);
- }
- if (!found) {
- console.log("did not find coins for remaining ", remaining.toJson());
- break;
- }
- }
-
- return new Promise<void>((resolve, reject) => {
- // Do the request one by one.
- let next = () => {
- if (workList.length == 0) {
- resolve();
- return;
- }
- let d = workList.pop();
- console.log("withdrawing", JSON.stringify(d));
- this.withdraw(d, reserve)
- .then(() => next())
- .catch((e) => {
- console.log("Failed to withdraw coin", e.stack);
- reject();
- });
- };
-
- // Asynchronous recursion
- next();
+ let denomsAvailable: Denomination[] = copy(mint.keys.denoms);
+ let denomsForWithdraw = getWithdrawDenomList(reserve.current_amount,
+ denomsAvailable);
+
+ let ps = denomsForWithdraw.map((denom) => {
+ console.log("withdrawing", JSON.stringify(denom));
+ // Do the withdraw asynchronously, so crypto is interleaved
+ // with requests
+ return this.withdraw(denom, reserve);
});
+
+ return Promise.all(ps).then(() => void 0);
}
- private updateReserve(reservePub: string, mint): Promise<Reserve> {
+ /**
+ * Update the information about a reserve that is stored in the wallet
+ * by quering the reserve's mint.
+ */
+ private updateReserve(reservePub: string, mint: Mint): Promise<Reserve> {
return Query(this.db)
.get("reserves", reservePub)
.then((reserve) => {
@@ -854,6 +873,10 @@ export class Wallet {
}
+ /**
+ * Retrieve a mapping from currency name to the amount
+ * that is currenctly available for spending in the wallet.
+ */
getBalances(): Promise<any> {
function collectBalances(c: Coin, byCurrency) {
let acc: AmountJson = byCurrency[c.currentAmount.currency];
@@ -872,7 +895,10 @@ export class Wallet {
}
- getHistory() {
+ /**
+ * Retrive the full event history for this wallet.
+ */
+ getHistory(): Promise<any[]> {
function collect(x, acc) {
acc.push(x);
return acc;