aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/util
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2022-10-12 15:58:10 -0300
committerSebastian <sebasjm@gmail.com>2022-10-12 15:58:10 -0300
commit610df1c9cf8ec91815130ac2a426f8f5b7d1ed0c (patch)
tree826f37de26f433c0842f6e5a793c454b60824fa8 /packages/taler-wallet-core/src/util
parentcb44202440313ea4405fbc74f4588144134a0821 (diff)
downloadwallet-core-610df1c9cf8ec91815130ac2a426f8f5b7d1ed0c.tar.xz
create a fee description timeline for global fee and wire fees
Diffstat (limited to 'packages/taler-wallet-core/src/util')
-rw-r--r--packages/taler-wallet-core/src/util/denominations.test.ts197
-rw-r--r--packages/taler-wallet-core/src/util/denominations.ts200
2 files changed, 250 insertions, 147 deletions
diff --git a/packages/taler-wallet-core/src/util/denominations.test.ts b/packages/taler-wallet-core/src/util/denominations.test.ts
index 31c561e88..9c93331a3 100644
--- a/packages/taler-wallet-core/src/util/denominations.test.ts
+++ b/packages/taler-wallet-core/src/util/denominations.test.ts
@@ -28,8 +28,9 @@ import {
} from "@gnu-taler/taler-util";
// import { expect } from "chai";
import {
- createDenominationPairTimeline,
- createDenominationTimeline,
+ createPairTimeline,
+ createTimeline,
+ selectBestForOverlappingDenominations,
} from "./denominations.js";
import test, { ExecutionContext } from "ava";
@@ -42,8 +43,14 @@ const VALUES = Array.from({ length: 10 }).map((undef, t) =>
const TIMESTAMPS = Array.from({ length: 20 }).map((undef, t_s) => ({ t_s }));
const ABS_TIME = TIMESTAMPS.map((m) => AbsoluteTime.fromTimestamp(m));
-function normalize(list: DenominationInfo[]): DenominationInfo[] {
- return list.map((e, idx) => ({ ...e, denomPubHash: `id${idx}` }));
+function normalize(
+ list: DenominationInfo[],
+): (DenominationInfo & { group: string })[] {
+ return list.map((e, idx) => ({
+ ...e,
+ denomPubHash: `id${idx}`,
+ group: Amounts.stringifyValue(e.value),
+ }));
}
//Avoiding to make an error-prone/time-consuming refactor
@@ -61,7 +68,7 @@ function expect(t: ExecutionContext, thing: any): any {
// describe("single value example", (t) => {
test("should have one row with start and exp", (t) => {
- const timeline = createDenominationTimeline(
+ const timeline = createTimeline(
normalize([
{
value: VALUES[1],
@@ -70,13 +77,17 @@ test("should have one row with start and exp", (t) => {
feeDeposit: VALUES[1],
} as Partial<DenominationInfo> as DenominationInfo,
]),
+ "denomPubHash",
+ "stampStart",
"stampExpireDeposit",
"feeDeposit",
+ "group",
+ selectBestForOverlappingDenominations,
);
expect(t, timeline).deep.equal([
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[1],
until: ABS_TIME[2],
fee: VALUES[1],
@@ -85,7 +96,7 @@ test("should have one row with start and exp", (t) => {
});
test("should have two rows with the second denom in the middle if second is better", (t) => {
- const timeline = createDenominationTimeline(
+ const timeline = createTimeline(
normalize([
{
value: VALUES[1],
@@ -100,19 +111,23 @@ test("should have two rows with the second denom in the middle if second is bett
feeDeposit: VALUES[2],
} as Partial<DenominationInfo> as DenominationInfo,
]),
+ "denomPubHash",
+ "stampStart",
"stampExpireDeposit",
"feeDeposit",
+ "group",
+ selectBestForOverlappingDenominations,
);
expect(t, timeline).deep.equal([
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[1],
until: ABS_TIME[3],
fee: VALUES[1],
},
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[3],
until: ABS_TIME[4],
fee: VALUES[2],
@@ -121,7 +136,7 @@ test("should have two rows with the second denom in the middle if second is bett
});
test("should have two rows with the first denom in the middle if second is worse", (t) => {
- const timeline = createDenominationTimeline(
+ const timeline = createTimeline(
normalize([
{
value: VALUES[1],
@@ -136,19 +151,23 @@ test("should have two rows with the first denom in the middle if second is worse
feeDeposit: VALUES[1],
} as Partial<DenominationInfo> as DenominationInfo,
]),
+ "denomPubHash",
+ "stampStart",
"stampExpireDeposit",
"feeDeposit",
+ "group",
+ selectBestForOverlappingDenominations,
);
expect(t, timeline).deep.equal([
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[1],
until: ABS_TIME[2],
fee: VALUES[2],
},
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[2],
until: ABS_TIME[4],
fee: VALUES[1],
@@ -157,7 +176,7 @@ test("should have two rows with the first denom in the middle if second is worse
});
test("should add a gap when there no fee", (t) => {
- const timeline = createDenominationTimeline(
+ const timeline = createTimeline(
normalize([
{
value: VALUES[1],
@@ -172,24 +191,28 @@ test("should add a gap when there no fee", (t) => {
feeDeposit: VALUES[1],
} as Partial<DenominationInfo> as DenominationInfo,
]),
+ "denomPubHash",
+ "stampStart",
"stampExpireDeposit",
"feeDeposit",
+ "group",
+ selectBestForOverlappingDenominations,
);
expect(t, timeline).deep.equal([
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[1],
until: ABS_TIME[2],
fee: VALUES[2],
},
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[2],
until: ABS_TIME[3],
},
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[3],
until: ABS_TIME[4],
fee: VALUES[1],
@@ -198,7 +221,7 @@ test("should add a gap when there no fee", (t) => {
});
test("should have three rows when first denom is between second and second is worse", (t) => {
- const timeline = createDenominationTimeline(
+ const timeline = createTimeline(
normalize([
{
value: VALUES[1],
@@ -213,24 +236,28 @@ test("should have three rows when first denom is between second and second is wo
feeDeposit: VALUES[2],
} as Partial<DenominationInfo> as DenominationInfo,
]),
+ "denomPubHash",
+ "stampStart",
"stampExpireDeposit",
"feeDeposit",
+ "group",
+ selectBestForOverlappingDenominations,
);
expect(t, timeline).deep.equal([
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[1],
until: ABS_TIME[2],
fee: VALUES[2],
},
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[2],
until: ABS_TIME[3],
fee: VALUES[1],
},
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[3],
until: ABS_TIME[4],
fee: VALUES[2],
@@ -239,7 +266,7 @@ test("should have three rows when first denom is between second and second is wo
});
test("should have one row when first denom is between second and second is better", (t) => {
- const timeline = createDenominationTimeline(
+ const timeline = createTimeline(
normalize([
{
value: VALUES[1],
@@ -254,13 +281,17 @@ test("should have one row when first denom is between second and second is bette
feeDeposit: VALUES[1],
} as Partial<DenominationInfo> as DenominationInfo,
]),
+ "denomPubHash",
+ "stampStart",
"stampExpireDeposit",
"feeDeposit",
+ "group",
+ selectBestForOverlappingDenominations,
);
expect(t, timeline).deep.equal([
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[1],
until: ABS_TIME[4],
fee: VALUES[1],
@@ -269,7 +300,7 @@ test("should have one row when first denom is between second and second is bette
});
test("should only add the best1", (t) => {
- const timeline = createDenominationTimeline(
+ const timeline = createTimeline(
normalize([
{
value: VALUES[1],
@@ -290,19 +321,23 @@ test("should only add the best1", (t) => {
feeDeposit: VALUES[2],
} as Partial<DenominationInfo> as DenominationInfo,
]),
+ "denomPubHash",
+ "stampStart",
"stampExpireDeposit",
"feeDeposit",
+ "group",
+ selectBestForOverlappingDenominations,
);
expect(t, timeline).deep.equal([
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[1],
until: ABS_TIME[2],
fee: VALUES[2],
},
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[2],
until: ABS_TIME[4],
fee: VALUES[1],
@@ -311,7 +346,7 @@ test("should only add the best1", (t) => {
});
test("should only add the best2", (t) => {
- const timeline = createDenominationTimeline(
+ const timeline = createTimeline(
normalize([
{
value: VALUES[1],
@@ -338,25 +373,29 @@ test("should only add the best2", (t) => {
feeDeposit: VALUES[3],
} as Partial<DenominationInfo> as DenominationInfo,
]),
+ "denomPubHash",
+ "stampStart",
"stampExpireDeposit",
"feeDeposit",
+ "group",
+ selectBestForOverlappingDenominations,
);
expect(t, timeline).deep.equal([
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[1],
until: ABS_TIME[2],
fee: VALUES[2],
},
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[2],
until: ABS_TIME[5],
fee: VALUES[1],
},
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[5],
until: ABS_TIME[6],
fee: VALUES[3],
@@ -365,7 +404,7 @@ test("should only add the best2", (t) => {
});
test("should only add the best3", (t) => {
- const timeline = createDenominationTimeline(
+ const timeline = createTimeline(
normalize([
{
value: VALUES[1],
@@ -386,13 +425,17 @@ test("should only add the best3", (t) => {
feeDeposit: VALUES[2],
} as Partial<DenominationInfo> as DenominationInfo,
]),
+ "denomPubHash",
+ "stampStart",
"stampExpireDeposit",
"feeDeposit",
+ "group",
+ selectBestForOverlappingDenominations,
);
expect(t, timeline).deep.equal([
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[2],
until: ABS_TIME[5],
fee: VALUES[1],
@@ -406,7 +449,7 @@ test("should only add the best3", (t) => {
//TODO: test the same start but different value
test("should not merge when there is different value", (t) => {
- const timeline = createDenominationTimeline(
+ const timeline = createTimeline(
normalize([
{
value: VALUES[1],
@@ -421,19 +464,23 @@ test("should not merge when there is different value", (t) => {
feeDeposit: VALUES[2],
} as Partial<DenominationInfo> as DenominationInfo,
]),
+ "denomPubHash",
+ "stampStart",
"stampExpireDeposit",
"feeDeposit",
+ "group",
+ selectBestForOverlappingDenominations,
);
expect(t, timeline).deep.equal([
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[1],
until: ABS_TIME[3],
fee: VALUES[1],
},
{
- value: VALUES[2],
+ group: Amounts.stringifyValue(VALUES[2]),
from: ABS_TIME[2],
until: ABS_TIME[4],
fee: VALUES[2],
@@ -442,7 +489,7 @@ test("should not merge when there is different value", (t) => {
});
test("should not merge when there is different value (with duplicates)", (t) => {
- const timeline = createDenominationTimeline(
+ const timeline = createTimeline(
normalize([
{
value: VALUES[1],
@@ -469,19 +516,23 @@ test("should not merge when there is different value (with duplicates)", (t) =>
feeDeposit: VALUES[2],
} as Partial<DenominationInfo> as DenominationInfo,
]),
+ "denomPubHash",
+ "stampStart",
"stampExpireDeposit",
"feeDeposit",
+ "group",
+ selectBestForOverlappingDenominations,
);
expect(t, timeline).deep.equal([
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[1],
until: ABS_TIME[3],
fee: VALUES[1],
},
{
- value: VALUES[2],
+ group: Amounts.stringifyValue(VALUES[2]),
from: ABS_TIME[2],
until: ABS_TIME[4],
fee: VALUES[2],
@@ -519,7 +570,7 @@ test("should return empty", (t) => {
const left = [] as FeeDescription[];
const right = [] as FeeDescription[];
- const pairs = createDenominationPairTimeline(left, right);
+ const pairs = createPairTimeline(left, right);
expect(t, pairs).deep.equals([]);
});
@@ -527,7 +578,7 @@ test("should return empty", (t) => {
test("should return first element", (t) => {
const left = [
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[1],
until: ABS_TIME[3],
fee: VALUES[1],
@@ -537,24 +588,24 @@ test("should return first element", (t) => {
const right = [] as FeeDescription[];
{
- const pairs = createDenominationPairTimeline(left, right);
+ const pairs = createPairTimeline(left, right);
expect(t, pairs).deep.equals([
{
from: ABS_TIME[1],
until: ABS_TIME[3],
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
left: VALUES[1],
right: undefined,
},
] as FeeDescriptionPair[]);
}
{
- const pairs = createDenominationPairTimeline(right, left);
+ const pairs = createPairTimeline(right, left);
expect(t, pairs).deep.equals([
{
from: ABS_TIME[1],
until: ABS_TIME[3],
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
right: VALUES[1],
left: undefined,
},
@@ -565,7 +616,7 @@ test("should return first element", (t) => {
test("should add both to the same row", (t) => {
const left = [
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[1],
until: ABS_TIME[3],
fee: VALUES[1],
@@ -574,7 +625,7 @@ test("should add both to the same row", (t) => {
const right = [
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[1],
until: ABS_TIME[3],
fee: VALUES[2],
@@ -582,24 +633,24 @@ test("should add both to the same row", (t) => {
] as FeeDescription[];
{
- const pairs = createDenominationPairTimeline(left, right);
+ const pairs = createPairTimeline(left, right);
expect(t, pairs).deep.equals([
{
from: ABS_TIME[1],
until: ABS_TIME[3],
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
left: VALUES[1],
right: VALUES[2],
},
] as FeeDescriptionPair[]);
}
{
- const pairs = createDenominationPairTimeline(right, left);
+ const pairs = createPairTimeline(right, left);
expect(t, pairs).deep.equals([
{
from: ABS_TIME[1],
until: ABS_TIME[3],
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
left: VALUES[2],
right: VALUES[1],
},
@@ -610,7 +661,7 @@ test("should add both to the same row", (t) => {
test("should repeat the first and change the second", (t) => {
const left = [
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[1],
until: ABS_TIME[5],
fee: VALUES[1],
@@ -619,18 +670,18 @@ test("should repeat the first and change the second", (t) => {
const right = [
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[1],
until: ABS_TIME[2],
fee: VALUES[2],
},
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[2],
until: ABS_TIME[3],
},
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[3],
until: ABS_TIME[4],
fee: VALUES[3],
@@ -638,33 +689,33 @@ test("should repeat the first and change the second", (t) => {
] as FeeDescription[];
{
- const pairs = createDenominationPairTimeline(left, right);
+ const pairs = createPairTimeline(left, right);
expect(t, pairs).deep.equals([
{
from: ABS_TIME[1],
until: ABS_TIME[2],
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
left: VALUES[1],
right: VALUES[2],
},
{
from: ABS_TIME[2],
until: ABS_TIME[3],
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
left: VALUES[1],
right: undefined,
},
{
from: ABS_TIME[3],
until: ABS_TIME[4],
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
left: VALUES[1],
right: VALUES[3],
},
{
from: ABS_TIME[4],
until: ABS_TIME[5],
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
left: VALUES[1],
right: undefined,
},
@@ -679,7 +730,7 @@ test("should repeat the first and change the second", (t) => {
test("should separate denominations of different value", (t) => {
const left = [
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[1],
until: ABS_TIME[3],
fee: VALUES[1],
@@ -688,7 +739,7 @@ test("should separate denominations of different value", (t) => {
const right = [
{
- value: VALUES[2],
+ group: Amounts.stringifyValue(VALUES[2]),
from: ABS_TIME[1],
until: ABS_TIME[3],
fee: VALUES[2],
@@ -696,38 +747,38 @@ test("should separate denominations of different value", (t) => {
] as FeeDescription[];
{
- const pairs = createDenominationPairTimeline(left, right);
+ const pairs = createPairTimeline(left, right);
expect(t, pairs).deep.equals([
{
from: ABS_TIME[1],
until: ABS_TIME[3],
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
left: VALUES[1],
right: undefined,
},
{
from: ABS_TIME[1],
until: ABS_TIME[3],
- value: VALUES[2],
+ group: Amounts.stringifyValue(VALUES[2]),
left: undefined,
right: VALUES[2],
},
] as FeeDescriptionPair[]);
}
{
- const pairs = createDenominationPairTimeline(right, left);
+ const pairs = createPairTimeline(right, left);
expect(t, pairs).deep.equals([
{
from: ABS_TIME[1],
until: ABS_TIME[3],
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
left: undefined,
right: VALUES[1],
},
{
from: ABS_TIME[1],
until: ABS_TIME[3],
- value: VALUES[2],
+ group: Amounts.stringifyValue(VALUES[2]),
left: VALUES[2],
right: undefined,
},
@@ -738,13 +789,13 @@ test("should separate denominations of different value", (t) => {
test("should separate denominations of different value2", (t) => {
const left = [
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[1],
until: ABS_TIME[2],
fee: VALUES[1],
},
{
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
from: ABS_TIME[2],
until: ABS_TIME[4],
fee: VALUES[2],
@@ -753,7 +804,7 @@ test("should separate denominations of different value2", (t) => {
const right = [
{
- value: VALUES[2],
+ group: Amounts.stringifyValue(VALUES[2]),
from: ABS_TIME[1],
until: ABS_TIME[3],
fee: VALUES[2],
@@ -761,26 +812,26 @@ test("should separate denominations of different value2", (t) => {
] as FeeDescription[];
{
- const pairs = createDenominationPairTimeline(left, right);
+ const pairs = createPairTimeline(left, right);
expect(t, pairs).deep.equals([
{
from: ABS_TIME[1],
until: ABS_TIME[2],
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
left: VALUES[1],
right: undefined,
},
{
from: ABS_TIME[2],
until: ABS_TIME[4],
- value: VALUES[1],
+ group: Amounts.stringifyValue(VALUES[1]),
left: VALUES[2],
right: undefined,
},
{
from: ABS_TIME[1],
until: ABS_TIME[3],
- value: VALUES[2],
+ group: Amounts.stringifyValue(VALUES[2]),
left: undefined,
right: VALUES[2],
},
diff --git a/packages/taler-wallet-core/src/util/denominations.ts b/packages/taler-wallet-core/src/util/denominations.ts
index 4efb902c8..9cd931acd 100644
--- a/packages/taler-wallet-core/src/util/denominations.ts
+++ b/packages/taler-wallet-core/src/util/denominations.ts
@@ -23,6 +23,7 @@ import {
FeeDescriptionPair,
TalerProtocolTimestamp,
TimePoint,
+ WireFee,
} from "@gnu-taler/taler-util";
/**
@@ -33,9 +34,9 @@ import {
* @param list denominations of same value
* @returns
*/
-function selectBestForOverlappingDenominations(
- list: DenominationInfo[],
-): DenominationInfo | undefined {
+export function selectBestForOverlappingDenominations<
+ T extends DenominationInfo,
+>(list: T[]): T | undefined {
let minDeposit: DenominationInfo | undefined = undefined;
//TODO: improve denomination selection, this is a trivial implementation
list.forEach((e) => {
@@ -50,6 +51,23 @@ function selectBestForOverlappingDenominations(
return minDeposit;
}
+export function selectMinimumFee<T extends { fee: AmountJson }>(
+ list: T[],
+): T | undefined {
+ let minFee: T | undefined = undefined;
+ //TODO: improve denomination selection, this is a trivial implementation
+ list.forEach((e) => {
+ if (minFee === undefined) {
+ minFee = e;
+ return;
+ }
+ if (Amounts.cmp(minFee.fee, e.fee) > -1) {
+ minFee = e;
+ }
+ });
+ return minFee;
+}
+
type PropsWithReturnType<T extends object, F> = Exclude<
{
[K in keyof T]: T[K] extends F ? K : never;
@@ -58,17 +76,19 @@ type PropsWithReturnType<T extends object, F> = Exclude<
>;
/**
- * Takes two list and create one with one timeline.
- * For any element in the position "p" on the left or right "list", then
- * list[p].until should be equal to list[p+1].from
+ * Takes two timelines and create one to compare them.
+ *
+ * For both lists the next condition should be true:
+ * for any element in the position "idx" then
+ * list[idx].until === list[idx+1].from
*
- * @see {createDenominationTimeline}
+ * @see {createTimeline}
*
* @param left list denominations @type {FeeDescription}
* @param right list denominations @type {FeeDescription}
* @returns list of pairs for the same time
*/
-export function createDenominationPairTimeline(
+export function createPairTimeline(
left: FeeDescription[],
right: FeeDescription[],
): FeeDescriptionPair[] {
@@ -81,23 +101,15 @@ export function createDenominationPairTimeline(
let ri = 0;
while (li < left.length && ri < right.length) {
- const currentValue =
- Amounts.cmp(left[li].value, right[ri].value) < 0
- ? left[li].value
- : right[ri].value;
+ const currentGroup =
+ left[li].group < right[ri].group ? left[li].group : right[ri].group;
let ll = 0; //left length (until next value)
- while (
- li + ll < left.length &&
- Amounts.cmp(left[li + ll].value, currentValue) === 0
- ) {
+ while (li + ll < left.length && left[li + ll].group === currentGroup) {
ll++;
}
let rl = 0; //right length (until next value)
- while (
- ri + rl < right.length &&
- Amounts.cmp(right[ri + rl].value, currentValue) === 0
- ) {
+ while (ri + rl < right.length && right[ri + rl].group === currentGroup) {
rl++;
}
const leftIsEmpty = ll === 0;
@@ -120,7 +132,7 @@ export function createDenominationPairTimeline(
right.splice(ri, 0, {
from: leftStarts,
until: ends,
- value: left[li].value,
+ group: left[li].group,
});
rl++;
@@ -132,7 +144,7 @@ export function createDenominationPairTimeline(
left.splice(li, 0, {
from: rightStarts,
until: ends,
- value: right[ri].value,
+ group: right[ri].group,
});
ll++;
@@ -148,7 +160,7 @@ export function createDenominationPairTimeline(
right.splice(ri + rl, 0, {
from: rightEnds,
until: leftEnds,
- value: left[0].value,
+ group: left[0].group,
});
rl++;
}
@@ -156,7 +168,7 @@ export function createDenominationPairTimeline(
left.splice(li + ll, 0, {
from: leftEnds,
until: rightEnds,
- value: right[0].value,
+ group: right[0].group,
});
ll++;
}
@@ -165,7 +177,7 @@ export function createDenominationPairTimeline(
while (
li < left.length &&
ri < right.length &&
- Amounts.cmp(left[li].value, right[ri].value) === 0
+ left[li].group === right[ri].group
) {
if (
AbsoluteTime.cmp(left[li].from, timeCut) !== 0 &&
@@ -186,7 +198,7 @@ export function createDenominationPairTimeline(
right: right[ri].fee,
from: timeCut,
until: AbsoluteTime.never(),
- value: currentValue,
+ group: currentGroup,
});
if (left[li].until.t_ms === right[ri].until.t_ms) {
@@ -204,7 +216,7 @@ export function createDenominationPairTimeline(
if (
li < left.length &&
- Amounts.cmp(left[li].value, pairList[pairList.length - 1].value) !== 0
+ left[li].group !== pairList[pairList.length - 1].group
) {
//value changed, should break
//this if will catch when both (left and right) change at the same time
@@ -217,7 +229,7 @@ export function createDenominationPairTimeline(
if (li < left.length) {
let timeCut =
pairList.length > 0 &&
- Amounts.cmp(pairList[pairList.length - 1].value, left[li].value) === 0
+ pairList[pairList.length - 1].group === left[li].group
? pairList[pairList.length - 1].until
: left[li].from;
while (li < left.length) {
@@ -226,7 +238,7 @@ export function createDenominationPairTimeline(
right: undefined,
from: timeCut,
until: left[li].until,
- value: left[li].value,
+ group: left[li].group,
});
timeCut = left[li].until;
li++;
@@ -235,7 +247,7 @@ export function createDenominationPairTimeline(
if (ri < right.length) {
let timeCut =
pairList.length > 0 &&
- Amounts.cmp(pairList[pairList.length - 1].value, right[ri].value) === 0
+ pairList[pairList.length - 1].group === right[ri].group
? pairList[pairList.length - 1].until
: right[ri].from;
while (ri < right.length) {
@@ -244,7 +256,7 @@ export function createDenominationPairTimeline(
left: undefined,
from: timeCut,
until: right[ri].until,
- value: right[ri].value,
+ group: right[ri].group,
});
timeCut = right[ri].until;
ri++;
@@ -254,42 +266,70 @@ export function createDenominationPairTimeline(
}
/**
- * Create a usage timeline with the denominations given.
+ * Create a usage timeline with the entity given.
*
- * If there are multiple denominations that can be used, the list will
- * contain the one that minimize the fee cost. @see selectBestForOverlappingDenominations
+ * If there are multiple entities that can be used in the same period,
+ * the list will contain the one that minimize the fee cost.
+ * @see selectBestForOverlappingDenominations
*
- * @param list list of denominations
- * @param periodProp property of element of the list that will be used as end of the usage period
+ * @param list list of entities
+ * @param idProp property used for identification
+ * @param periodStartProp property of element of the list that will be used as start of the usage period
+ * @param periodEndProp property of element of the list that will be used as end of the usage period
* @param feeProp property of the element of the list that will be used as fee reference
+ * @param groupProp property of the element of the list that will be used for grouping
* @returns list of @type {FeeDescription} sorted by usage period
*/
-export function createDenominationTimeline(
- list: DenominationInfo[],
- periodProp: PropsWithReturnType<DenominationInfo, TalerProtocolTimestamp>,
- feeProp: PropsWithReturnType<DenominationInfo, AmountJson>,
+export function createTimeline<Type extends object>(
+ list: Type[],
+ idProp: PropsWithReturnType<Type, string>,
+ periodStartProp: PropsWithReturnType<Type, TalerProtocolTimestamp>,
+ periodEndProp: PropsWithReturnType<Type, TalerProtocolTimestamp>,
+ feeProp: PropsWithReturnType<Type, AmountJson>,
+ groupProp: PropsWithReturnType<Type, string> | undefined,
+ selectBestForOverlapping: (l: Type[]) => Type | undefined,
): FeeDescription[] {
- const points = list
+ /**
+ * First we create a list with with point in the timeline sorted
+ * by time and categorized by starting or ending.
+ */
+ const sortedPointsInTime = list
.reduce((ps, denom) => {
//exclude denoms with bad configuration
- if (denom.stampStart.t_s >= denom[periodProp].t_s) {
- throw Error(`denom ${denom.denomPubHash} has start after the end`);
- // return ps;
+ const id = denom[idProp] as string;
+ const stampStart = denom[periodStartProp] as TalerProtocolTimestamp;
+ const stampEnd = denom[periodEndProp] as TalerProtocolTimestamp;
+ const fee = denom[feeProp] as AmountJson;
+ const group = !groupProp ? "" : (denom[groupProp] as string);
+
+ if (!id) {
+ throw Error(
+ `denomination without hash ${JSON.stringify(denom, undefined, 2)}`,
+ );
+ }
+ if (stampStart.t_s >= stampEnd.t_s) {
+ throw Error(`denom ${id} has start after the end`);
}
ps.push({
type: "start",
- moment: AbsoluteTime.fromTimestamp(denom.stampStart),
+ fee,
+ group,
+ id,
+ moment: AbsoluteTime.fromTimestamp(stampStart),
denom,
});
ps.push({
type: "end",
- moment: AbsoluteTime.fromTimestamp(denom[periodProp]),
+ fee,
+ group,
+ id,
+ moment: AbsoluteTime.fromTimestamp(stampEnd),
denom,
});
return ps;
- }, [] as TimePoint[])
+ }, [] as TimePoint<Type>[])
.sort((a, b) => {
- const v = Amounts.cmp(a.denom.value, b.denom.value);
+ const v = a.group == b.group ? 0 : a.group > b.group ? 1 : -1;
if (v != 0) return v;
const t = AbsoluteTime.cmp(a.moment, b.moment);
if (t != 0) return t;
@@ -297,21 +337,15 @@ export function createDenominationTimeline(
return a.type === "start" ? 1 : -1;
});
- const activeAtTheSameTime: DenominationInfo[] = [];
- return points.reduce((result, cursor, idx) => {
- const hash = cursor.denom.denomPubHash;
- if (!hash)
- throw Error(
- `denomination without hash ${JSON.stringify(
- cursor.denom,
- undefined,
- 2,
- )}`,
- );
-
+ const activeAtTheSameTime: Type[] = [];
+ return sortedPointsInTime.reduce((result, cursor, idx) => {
+ /**
+ * Now that we have move one step forward, we should
+ * update the previous element ending period with the
+ * current start time.
+ */
let prev = result.length > 0 ? result[result.length - 1] : undefined;
- const prevHasSameValue =
- prev && Amounts.cmp(prev.value, cursor.denom.value) === 0;
+ const prevHasSameValue = prev && prev.group == cursor.group;
if (prev) {
if (prevHasSameValue) {
prev.until = cursor.moment;
@@ -326,11 +360,15 @@ export function createDenominationTimeline(
}
}
- //update the activeAtTheSameTime list
+ /**
+ * With the current moment in the iteration we
+ * should keep updated which entities are current
+ * active in this period of time.
+ */
if (cursor.type === "end") {
- const loc = activeAtTheSameTime.findIndex((v) => v.denomPubHash === hash);
+ const loc = activeAtTheSameTime.findIndex((v) => v[idProp] === cursor.id);
if (loc === -1) {
- throw Error(`denomination ${hash} has an end but no start`);
+ throw Error(`denomination ${cursor.id} has an end but no start`);
}
activeAtTheSameTime.splice(loc, 1);
} else if (cursor.type === "start") {
@@ -340,12 +378,16 @@ export function createDenominationTimeline(
throw new Error(`not TimePoint defined for type: ${exhaustiveCheck}`);
}
- if (idx == points.length - 1) {
- //this is the last element in the list, prevent adding
- //a gap in the end
+ if (idx == sortedPointsInTime.length - 1) {
+ /**
+ * This is the last element in the list, if we continue
+ * a gap will normally be added which is not necessary.
+ * Also, the last element should be ending and the list of active
+ * element should be empty
+ */
if (cursor.type !== "end") {
throw Error(
- `denomination ${hash} starts after ending or doesn't have an ending`,
+ `denomination ${cursor.id} starts after ending or doesn't have an ending`,
);
}
if (activeAtTheSameTime.length > 0) {
@@ -356,26 +398,36 @@ export function createDenominationTimeline(
return result;
}
- const current = selectBestForOverlappingDenominations(activeAtTheSameTime);
+ const current = selectBestForOverlapping(activeAtTheSameTime);
if (current) {
+ /**
+ * We have a candidate to add in the list, check that we are
+ * not adding a duplicate.
+ * Next element in the list will defined the ending.
+ */
+ const currentFee = current[feeProp] as AmountJson;
if (
prev === undefined || //is the first
!prev.fee || //is a gap
- Amounts.cmp(prev.fee, current[feeProp]) !== 0 // prev has the same fee
+ Amounts.cmp(prev.fee, currentFee) !== 0 // prev has different fee
) {
result.push({
- value: cursor.denom.value,
+ group: cursor.group,
from: cursor.moment,
until: AbsoluteTime.never(), //not yet known
- fee: current[feeProp],
+ fee: currentFee,
});
} else {
prev.until = cursor.moment;
}
} else {
+ /**
+ * No active element in this period of time, so we add a gap (no fee)
+ * Next element in the list will defined the ending.
+ */
result.push({
- value: cursor.denom.value,
+ group: cursor.group,
from: cursor.moment,
until: AbsoluteTime.never(), //not yet known
});