aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/util
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-core/src/util')
-rw-r--r--packages/taler-wallet-core/src/util/coinSelection.test.ts244
-rw-r--r--packages/taler-wallet-core/src/util/coinSelection.ts25
-rw-r--r--packages/taler-wallet-core/src/util/denominations.ts9
-rw-r--r--packages/taler-wallet-core/src/util/instructedAmountConversion.ts17
4 files changed, 193 insertions, 102 deletions
diff --git a/packages/taler-wallet-core/src/util/coinSelection.test.ts b/packages/taler-wallet-core/src/util/coinSelection.test.ts
index 2a322c4a9..81a656f8a 100644
--- a/packages/taler-wallet-core/src/util/coinSelection.test.ts
+++ b/packages/taler-wallet-core/src/util/coinSelection.test.ts
@@ -15,65 +15,58 @@
*/
import {
AbsoluteTime,
- AgeRestriction,
- AmountJson,
AmountString,
Amounts,
DenomKeyType,
Duration,
- TransactionAmountMode,
+ j2s,
} from "@gnu-taler/taler-util";
import test, { ExecutionContext } from "ava";
-import { AvailableDenom, testing_greedySelectPeer } from "./coinSelection.js"
-
-type Tester<T> = {
- deep: {
- equal(another: T): ReturnType<ExecutionContext["deepEqual"]>;
- equals(another: T): ReturnType<ExecutionContext["deepEqual"]>;
- }
-}
-
-function expect<T>(t: ExecutionContext, thing: T): Tester<T> {
- return {
- deep: {
- equal: (another: T) => t.deepEqual(thing, another),
- equals: (another: T) => t.deepEqual(thing, another),
- },
- };
-}
+import {
+ AvailableDenom,
+ testing_greedySelectPeer,
+ testing_selectGreedy,
+} from "./coinSelection.js";
const inTheDistantFuture = AbsoluteTime.toProtocolTimestamp(
- AbsoluteTime.addDuration(AbsoluteTime.now(), Duration.fromSpec({ hours: 1 }))
-)
+ AbsoluteTime.addDuration(AbsoluteTime.now(), Duration.fromSpec({ hours: 1 })),
+);
const inThePast = AbsoluteTime.toProtocolTimestamp(
- AbsoluteTime.subtractDuraction(AbsoluteTime.now(), Duration.fromSpec({ hours: 1 }))
-)
-
-test("should select the coin", (t) => {
- const instructedAmount = Amounts.parseOrThrow("LOCAL:2")
+ AbsoluteTime.subtractDuraction(
+ AbsoluteTime.now(),
+ Duration.fromSpec({ hours: 1 }),
+ ),
+);
+
+test("p2p: should select the coin", (t) => {
+ const instructedAmount = Amounts.parseOrThrow("LOCAL:2");
const tally = {
amountAcc: Amounts.zeroOfCurrency(instructedAmount.currency),
depositFeesAcc: Amounts.zeroOfCurrency(instructedAmount.currency),
lastDepositFee: Amounts.zeroOfCurrency(instructedAmount.currency),
};
const coins = testing_greedySelectPeer(
- createCandidates([{
- amount: "LOCAL:10",
- numAvailable: 5,
- depositFee: "LOCAL:0.1",
- fromExchange: "http://exchange.localhost/",
- }]),
- instructedAmount,
- tally
+ createCandidates([
+ {
+ amount: "LOCAL:10",
+ numAvailable: 5,
+ depositFee: "LOCAL:0.1",
+ fromExchange: "http://exchange.localhost/",
+ },
+ ]),
+ instructedAmount,
+ tally,
);
+ t.log(j2s(coins));
+
expect(t, coins).deep.equal({
"hash0;32;http://exchange.localhost/": {
exchangeBaseUrl: "http://exchange.localhost/",
denomPubHash: "hash0",
maxAge: 32,
- contributions: [Amounts.parseOrThrow("LOCAL:2")],
- }
+ contributions: [Amounts.parseOrThrow("LOCAL:2.1")],
+ },
});
expect(t, tally).deep.equal({
@@ -81,25 +74,26 @@ test("should select the coin", (t) => {
depositFeesAcc: Amounts.parseOrThrow("LOCAL:0.1"),
lastDepositFee: Amounts.parseOrThrow("LOCAL:0.1"),
});
-
});
-test("should select 3 coins", (t) => {
- const instructedAmount = Amounts.parseOrThrow("LOCAL:20")
+test("p2p: should select 3 coins", (t) => {
+ const instructedAmount = Amounts.parseOrThrow("LOCAL:20");
const tally = {
amountAcc: Amounts.zeroOfCurrency(instructedAmount.currency),
depositFeesAcc: Amounts.zeroOfCurrency(instructedAmount.currency),
lastDepositFee: Amounts.zeroOfCurrency(instructedAmount.currency),
};
const coins = testing_greedySelectPeer(
- createCandidates([{
- amount: "LOCAL:10",
- numAvailable: 5,
- depositFee: "LOCAL:0.1",
- fromExchange: "http://exchange.localhost/",
- }]),
- instructedAmount,
- tally
+ createCandidates([
+ {
+ amount: "LOCAL:10",
+ numAvailable: 5,
+ depositFee: "LOCAL:0.1",
+ fromExchange: "http://exchange.localhost/",
+ },
+ ]),
+ instructedAmount,
+ tally,
);
expect(t, coins).deep.equal({
@@ -110,9 +104,9 @@ test("should select 3 coins", (t) => {
contributions: [
Amounts.parseOrThrow("LOCAL:9.9"),
Amounts.parseOrThrow("LOCAL:9.9"),
- Amounts.parseOrThrow("LOCAL:0.2")
+ Amounts.parseOrThrow("LOCAL:0.5"),
],
- }
+ },
});
expect(t, tally).deep.equal({
@@ -120,62 +114,142 @@ test("should select 3 coins", (t) => {
depositFeesAcc: Amounts.parseOrThrow("LOCAL:0.3"),
lastDepositFee: Amounts.parseOrThrow("LOCAL:0.1"),
});
-
});
-test("can't select since the instructed amount is too high", (t) => {
- const instructedAmount = Amounts.parseOrThrow("LOCAL:60")
+test("p2p: can't select since the instructed amount is too high", (t) => {
+ const instructedAmount = Amounts.parseOrThrow("LOCAL:60");
const tally = {
amountAcc: Amounts.zeroOfCurrency(instructedAmount.currency),
depositFeesAcc: Amounts.zeroOfCurrency(instructedAmount.currency),
lastDepositFee: Amounts.zeroOfCurrency(instructedAmount.currency),
};
const coins = testing_greedySelectPeer(
- createCandidates([{
- amount: "LOCAL:10",
- numAvailable: 5,
- depositFee: "LOCAL:0.1",
- fromExchange: "http://exchange.localhost/",
- }]),
- instructedAmount,
- tally
+ createCandidates([
+ {
+ amount: "LOCAL:10",
+ numAvailable: 5,
+ depositFee: "LOCAL:0.1",
+ fromExchange: "http://exchange.localhost/",
+ },
+ ]),
+ instructedAmount,
+ tally,
);
expect(t, coins).deep.equal(undefined);
expect(t, tally).deep.equal({
- amountAcc: Amounts.parseOrThrow("LOCAL:49.5"),
+ amountAcc: Amounts.parseOrThrow("LOCAL:49"),
depositFeesAcc: Amounts.parseOrThrow("LOCAL:0.5"),
lastDepositFee: Amounts.parseOrThrow("LOCAL:0.1"),
});
-
});
+test("pay: select one coin to pay with fee", (t) => {
+ const payment = Amounts.parseOrThrow("LOCAL:2");
+ const exchangeWireFee = Amounts.parseOrThrow("LOCAL:0.1");
+ const zero = Amounts.zeroOfCurrency(payment.currency);
+ const tally = {
+ amountPayRemaining: payment,
+ amountWireFeeLimitRemaining: zero,
+ amountDepositFeeLimitRemaining: zero,
+ customerDepositFees: zero,
+ customerWireFees: zero,
+ wireFeeCoveredForExchange: new Set<string>(),
+ lastDepositFee: zero,
+ };
+ const coins = testing_selectGreedy(
+ {
+ auditors: [],
+ exchanges: [
+ {
+ exchangeBaseUrl: "http://exchange.localhost/",
+ exchangePub: "E5M8CGRDHXF1RCVP3B8TQCTDYNQ7T4XHWR5SVEQRGVVMVME41VJ0",
+ },
+ ],
+ contractTermsAmount: payment,
+ depositFeeLimit: zero,
+ wireFeeAmortization: 1,
+ wireFeeLimit: zero,
+ prevPayCoins: [],
+ wireMethod: "x-taler-bank",
+ },
+ createCandidates([
+ {
+ amount: "LOCAL:10",
+ numAvailable: 5,
+ depositFee: "LOCAL:0.1",
+ fromExchange: "http://exchange.localhost/",
+ },
+ ]),
+ { "http://exchange.localhost/": exchangeWireFee },
+ tally,
+ );
+ expect(t, coins).deep.equal({
+ "hash0;32;http://exchange.localhost/": {
+ exchangeBaseUrl: "http://exchange.localhost/",
+ denomPubHash: "hash0",
+ maxAge: 32,
+ contributions: [Amounts.parseOrThrow("LOCAL:2.2")],
+ },
+ });
+ expect(t, tally).deep.equal({
+ amountPayRemaining: Amounts.parseOrThrow("LOCAL:2"),
+ amountWireFeeLimitRemaining: zero,
+ amountDepositFeeLimitRemaining: zero,
+ customerDepositFees: zero,
+ customerWireFees: zero,
+ wireFeeCoveredForExchange: new Set(),
+ lastDepositFee: zero,
+ });
+});
-function createCandidates(ar: {amount: AmountString, depositFee: AmountString, numAvailable: number, fromExchange: string}[]): AvailableDenom[] {
- return ar.map((r,idx) => {
+function createCandidates(
+ ar: {
+ amount: AmountString;
+ depositFee: AmountString;
+ numAvailable: number;
+ fromExchange: string;
+ }[],
+): AvailableDenom[] {
+ return ar.map((r, idx) => {
return {
- "denomPub": {
- "age_mask": 0,
- "cipher": DenomKeyType.Rsa,
- "rsa_public_key": "PPP"
+ denomPub: {
+ age_mask: 0,
+ cipher: DenomKeyType.Rsa,
+ rsa_public_key: "PPP",
},
- "denomPubHash": `hash${idx}`,
- "value": r.amount,
- "feeDeposit": r.depositFee,
- "feeRefresh": "LOCAL:0",
- "feeRefund": "LOCAL:0",
- "feeWithdraw": "LOCAL:0",
- "stampExpireDeposit": inTheDistantFuture,
- "stampExpireLegal": inTheDistantFuture,
- "stampExpireWithdraw": inTheDistantFuture,
- "stampStart": inThePast,
- "exchangeBaseUrl": r.fromExchange,
- "numAvailable": r.numAvailable,
- "maxAge": 32,
-
- }
- })
+ denomPubHash: `hash${idx}`,
+ value: r.amount,
+ feeDeposit: r.depositFee,
+ feeRefresh: "LOCAL:0",
+ feeRefund: "LOCAL:0",
+ feeWithdraw: "LOCAL:0",
+ stampExpireDeposit: inTheDistantFuture,
+ stampExpireLegal: inTheDistantFuture,
+ stampExpireWithdraw: inTheDistantFuture,
+ stampStart: inThePast,
+ exchangeBaseUrl: r.fromExchange,
+ numAvailable: r.numAvailable,
+ maxAge: 32,
+ };
+ });
+}
+
+type Tester<T> = {
+ deep: {
+ equal(another: T): ReturnType<ExecutionContext["deepEqual"]>;
+ equals(another: T): ReturnType<ExecutionContext["deepEqual"]>;
+ };
+};
+
+function expect<T>(t: ExecutionContext, thing: T): Tester<T> {
+ return {
+ deep: {
+ equal: (another: T) => t.deepEqual(thing, another),
+ equals: (another: T) => t.deepEqual(thing, another),
+ },
+ };
}
diff --git a/packages/taler-wallet-core/src/util/coinSelection.ts b/packages/taler-wallet-core/src/util/coinSelection.ts
index 0885215dd..8c90f26f1 100644
--- a/packages/taler-wallet-core/src/util/coinSelection.ts
+++ b/packages/taler-wallet-core/src/util/coinSelection.ts
@@ -419,6 +419,11 @@ interface SelResult {
};
}
+export function testing_selectGreedy(
+ ...args: Parameters<typeof selectGreedy>
+): ReturnType<typeof selectGreedy> {
+ return selectGreedy(...args);
+}
function selectGreedy(
req: SelectPayCoinRequestNg,
candidateDenoms: AvailableDenom[],
@@ -897,9 +902,12 @@ interface PeerCoinSelectionTally {
/**
* exporting for testing
*/
-export function testing_greedySelectPeer(...args: Parameters<typeof greedySelectPeer>): ReturnType<typeof greedySelectPeer> {
- return greedySelectPeer(...args)
+export function testing_greedySelectPeer(
+ ...args: Parameters<typeof greedySelectPeer>
+): ReturnType<typeof greedySelectPeer> {
+ return greedySelectPeer(...args);
}
+
function greedySelectPeer(
candidates: AvailableDenom[],
instructedAmount: AmountLike,
@@ -918,11 +926,16 @@ function greedySelectPeer(
instructedAmount,
tally.amountAcc,
).amount;
- const coinContrib = Amounts.sub(denom.value, denom.feeDeposit).amount
+ // Maximum amount the coin could effectively contribute.
+ const maxCoinContrib = Amounts.sub(denom.value, denom.feeDeposit).amount;
+
+ const coinSpend = Amounts.min(
+ Amounts.add(amountPayRemaining, denom.feeDeposit).amount,
+ maxCoinContrib,
+ );
- const coinSpend = Amounts.min(amountPayRemaining, coinContrib)
-
tally.amountAcc = Amounts.add(tally.amountAcc, coinSpend).amount;
+ tally.amountAcc = Amounts.sub(tally.amountAcc, denom.feeDeposit).amount;
tally.depositFeesAcc = Amounts.add(
tally.depositFeesAcc,
@@ -930,7 +943,7 @@ function greedySelectPeer(
).amount;
tally.lastDepositFee = Amounts.parseOrThrow(denom.feeDeposit);
-
+
contributions.push(coinSpend);
}
if (contributions.length > 0) {
diff --git a/packages/taler-wallet-core/src/util/denominations.ts b/packages/taler-wallet-core/src/util/denominations.ts
index 76716cf7a..db6e69956 100644
--- a/packages/taler-wallet-core/src/util/denominations.ts
+++ b/packages/taler-wallet-core/src/util/denominations.ts
@@ -26,10 +26,9 @@ import {
FeeDescriptionPair,
TalerProtocolTimestamp,
TimePoint,
- WireFee,
} from "@gnu-taler/taler-util";
import { DenominationRecord } from "../db.js";
-import { WalletConfig } from "../index.js";
+import { timestampProtocolFromDb } from "../index.js";
/**
* Given a list of denominations with the same value and same period of time:
@@ -457,9 +456,11 @@ export function isWithdrawableDenom(
denomselAllowLate?: boolean,
): boolean {
const now = AbsoluteTime.now();
- const start = AbsoluteTime.fromProtocolTimestamp(d.stampStart);
+ const start = AbsoluteTime.fromProtocolTimestamp(
+ timestampProtocolFromDb(d.stampStart),
+ );
const withdrawExpire = AbsoluteTime.fromProtocolTimestamp(
- d.stampExpireWithdraw,
+ timestampProtocolFromDb(d.stampExpireWithdraw),
);
const started = AbsoluteTime.cmp(now, start) >= 0;
let lastPossibleWithdraw: AbsoluteTime;
diff --git a/packages/taler-wallet-core/src/util/instructedAmountConversion.ts b/packages/taler-wallet-core/src/util/instructedAmountConversion.ts
index 54c08eee4..a0394a687 100644
--- a/packages/taler-wallet-core/src/util/instructedAmountConversion.ts
+++ b/packages/taler-wallet-core/src/util/instructedAmountConversion.ts
@@ -14,6 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
+import { GlobalIDB } from "@gnu-taler/idb-bridge";
import {
AbsoluteTime,
AgeRestriction,
@@ -29,14 +30,14 @@ import {
parsePaytoUri,
strcmp,
} from "@gnu-taler/taler-util";
-import { checkDbInvariant } from "./invariants.js";
import {
DenominationRecord,
InternalWalletState,
getExchangeDetails,
+ timestampProtocolFromDb,
} from "../index.js";
import { CoinInfo } from "./coinSelection.js";
-import { GlobalIDB } from "@gnu-taler/idb-bridge";
+import { checkDbInvariant } from "./invariants.js";
/**
* If the operation going to be plan subtracts
@@ -224,10 +225,10 @@ async function getAvailableDenoms(
);
for (const denom of ds) {
const expiresWithdraw = AbsoluteTime.fromProtocolTimestamp(
- denom.stampExpireWithdraw,
+ timestampProtocolFromDb(denom.stampExpireWithdraw),
);
const expiresDeposit = AbsoluteTime.fromProtocolTimestamp(
- denom.stampExpireDeposit,
+ timestampProtocolFromDb(denom.stampExpireDeposit),
);
creditDeadline = AbsoluteTime.min(deadline, expiresWithdraw);
debitDeadline = AbsoluteTime.min(deadline, expiresDeposit);
@@ -270,10 +271,10 @@ async function getAvailableDenoms(
continue;
}
const expiresWithdraw = AbsoluteTime.fromProtocolTimestamp(
- denom.stampExpireWithdraw,
+ timestampProtocolFromDb(denom.stampExpireWithdraw),
);
const expiresDeposit = AbsoluteTime.fromProtocolTimestamp(
- denom.stampExpireDeposit,
+ timestampProtocolFromDb(denom.stampExpireDeposit),
);
creditDeadline = AbsoluteTime.min(deadline, expiresWithdraw);
debitDeadline = AbsoluteTime.min(deadline, expiresDeposit);
@@ -318,7 +319,9 @@ function buildCoinInfoFromDenom(
exchangeBaseUrl: denom.exchangeBaseUrl,
duration: AbsoluteTime.difference(
AbsoluteTime.now(),
- AbsoluteTime.fromProtocolTimestamp(denom.stampExpireDeposit),
+ AbsoluteTime.fromProtocolTimestamp(
+ timestampProtocolFromDb(denom.stampExpireDeposit),
+ ),
),
totalAvailable: total,
value: Amounts.parseOrThrow(denom.value),