2022-09-12 15:57:13 +02:00
|
|
|
/*
|
|
|
|
This file is part of GNU Taler
|
|
|
|
(C) 2021 Taler Systems S.A.
|
|
|
|
|
|
|
|
GNU Taler is free software; you can redistribute it and/or modify it under the
|
|
|
|
terms of the GNU General Public License as published by the Free Software
|
|
|
|
Foundation; either version 3, or (at your option) any later version.
|
|
|
|
|
|
|
|
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
|
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License along with
|
|
|
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
|
|
|
*/
|
|
|
|
|
2022-09-16 19:27:24 +02:00
|
|
|
import {
|
|
|
|
AbsoluteTime,
|
|
|
|
AmountJson,
|
|
|
|
Amounts,
|
2022-11-02 17:42:14 +01:00
|
|
|
AmountString,
|
2022-09-16 19:27:24 +02:00
|
|
|
DenominationInfo,
|
|
|
|
FeeDescription,
|
|
|
|
FeeDescriptionPair,
|
|
|
|
TalerProtocolTimestamp,
|
|
|
|
TimePoint,
|
2022-10-12 20:58:10 +02:00
|
|
|
WireFee,
|
2022-09-16 19:27:24 +02:00
|
|
|
} from "@gnu-taler/taler-util";
|
2022-09-12 15:57:13 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a list of denominations with the same value and same period of time:
|
|
|
|
* return the one that will be used.
|
|
|
|
* The best denomination is the one that will minimize the fee cost.
|
|
|
|
*
|
|
|
|
* @param list denominations of same value
|
|
|
|
* @returns
|
|
|
|
*/
|
2022-10-12 20:58:10 +02:00
|
|
|
export function selectBestForOverlappingDenominations<
|
|
|
|
T extends DenominationInfo,
|
|
|
|
>(list: T[]): T | undefined {
|
2022-09-12 15:57:13 +02:00
|
|
|
let minDeposit: DenominationInfo | undefined = undefined;
|
|
|
|
//TODO: improve denomination selection, this is a trivial implementation
|
|
|
|
list.forEach((e) => {
|
|
|
|
if (minDeposit === undefined) {
|
|
|
|
minDeposit = e;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (Amounts.cmp(minDeposit.feeDeposit, e.feeDeposit) > -1) {
|
|
|
|
minDeposit = e;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return minDeposit;
|
|
|
|
}
|
|
|
|
|
2022-11-02 17:42:14 +01:00
|
|
|
export function selectMinimumFee<T extends { fee: AmountString }>(
|
2022-10-12 20:58:10 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-09-12 15:57:13 +02:00
|
|
|
type PropsWithReturnType<T extends object, F> = Exclude<
|
|
|
|
{
|
|
|
|
[K in keyof T]: T[K] extends F ? K : never;
|
|
|
|
}[keyof T],
|
|
|
|
undefined
|
|
|
|
>;
|
|
|
|
|
|
|
|
/**
|
2022-10-12 20:58:10 +02:00
|
|
|
* 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
|
2022-09-16 19:27:24 +02:00
|
|
|
*
|
2022-10-12 20:58:10 +02:00
|
|
|
* @see {createTimeline}
|
2022-09-16 19:27:24 +02:00
|
|
|
*
|
2022-09-12 15:57:13 +02:00
|
|
|
* @param left list denominations @type {FeeDescription}
|
|
|
|
* @param right list denominations @type {FeeDescription}
|
|
|
|
* @returns list of pairs for the same time
|
|
|
|
*/
|
2022-10-12 20:58:10 +02:00
|
|
|
export function createPairTimeline(
|
2022-09-16 19:27:24 +02:00
|
|
|
left: FeeDescription[],
|
|
|
|
right: FeeDescription[],
|
|
|
|
): FeeDescriptionPair[] {
|
2022-11-22 19:15:40 +01:00
|
|
|
//FIXME: we need to create a copy of the array because
|
|
|
|
//this algorithm is using splice, remove splice and
|
2022-11-18 15:29:24 +01:00
|
|
|
//remove this array duplication
|
2022-11-22 19:15:40 +01:00
|
|
|
left = [...left];
|
|
|
|
right = [...right];
|
2022-11-18 15:29:24 +01:00
|
|
|
|
2022-09-12 15:57:13 +02:00
|
|
|
//both list empty, discarded
|
|
|
|
if (left.length === 0 && right.length === 0) return [];
|
|
|
|
|
|
|
|
const pairList: FeeDescriptionPair[] = [];
|
|
|
|
|
2022-11-18 15:29:24 +01:00
|
|
|
let li = 0; //left list index
|
|
|
|
let ri = 0; //right list index
|
2022-09-12 15:57:13 +02:00
|
|
|
|
|
|
|
while (li < left.length && ri < right.length) {
|
2022-11-22 19:15:40 +01:00
|
|
|
const currentGroup =
|
|
|
|
Number.parseFloat(left[li].group) < Number.parseFloat(right[ri].group)
|
|
|
|
? left[li].group
|
|
|
|
: right[ri].group;
|
2022-11-18 15:29:24 +01:00
|
|
|
const lgs = li; //left group start index
|
|
|
|
const rgs = ri; //right group start index
|
2022-09-12 15:57:13 +02:00
|
|
|
|
2022-11-18 15:29:24 +01:00
|
|
|
let lgl = 0; //left group length (until next value)
|
|
|
|
while (li + lgl < left.length && left[li + lgl].group === currentGroup) {
|
|
|
|
lgl++;
|
2022-09-12 15:57:13 +02:00
|
|
|
}
|
2022-11-18 15:29:24 +01:00
|
|
|
let rgl = 0; //right group length (until next value)
|
|
|
|
while (ri + rgl < right.length && right[ri + rgl].group === currentGroup) {
|
|
|
|
rgl++;
|
2022-09-12 15:57:13 +02:00
|
|
|
}
|
2022-11-18 15:29:24 +01:00
|
|
|
const leftGroupIsEmpty = lgl === 0;
|
|
|
|
const rightGroupIsEmpty = rgl === 0;
|
2022-09-12 15:57:13 +02:00
|
|
|
//check which start after, add gap so both list starts at the same time
|
|
|
|
// one list may be empty
|
2022-11-18 15:29:24 +01:00
|
|
|
const leftStartTime: AbsoluteTime = leftGroupIsEmpty
|
2022-09-16 19:27:24 +02:00
|
|
|
? { t_ms: "never" }
|
|
|
|
: left[li].from;
|
2022-11-18 15:29:24 +01:00
|
|
|
const rightStartTime: AbsoluteTime = rightGroupIsEmpty
|
2022-09-16 19:27:24 +02:00
|
|
|
? { t_ms: "never" }
|
|
|
|
: right[ri].from;
|
2022-09-12 15:57:13 +02:00
|
|
|
|
|
|
|
//first time cut is the smallest time
|
2022-11-18 15:29:24 +01:00
|
|
|
let timeCut: AbsoluteTime = leftStartTime;
|
2022-09-12 15:57:13 +02:00
|
|
|
|
2022-11-18 15:29:24 +01:00
|
|
|
if (AbsoluteTime.cmp(leftStartTime, rightStartTime) < 0) {
|
|
|
|
const ends = rightGroupIsEmpty ? left[li + lgl - 1].until : right[0].from;
|
2022-09-12 15:57:13 +02:00
|
|
|
|
|
|
|
right.splice(ri, 0, {
|
2022-11-18 15:29:24 +01:00
|
|
|
from: leftStartTime,
|
2022-09-12 15:57:13 +02:00
|
|
|
until: ends,
|
2022-10-12 20:58:10 +02:00
|
|
|
group: left[li].group,
|
2022-09-12 15:57:13 +02:00
|
|
|
});
|
2022-11-18 15:29:24 +01:00
|
|
|
rgl++;
|
2022-09-12 15:57:13 +02:00
|
|
|
|
2022-11-18 15:29:24 +01:00
|
|
|
timeCut = leftStartTime;
|
2022-09-12 15:57:13 +02:00
|
|
|
}
|
2022-11-18 15:29:24 +01:00
|
|
|
if (AbsoluteTime.cmp(leftStartTime, rightStartTime) > 0) {
|
|
|
|
const ends = leftGroupIsEmpty ? right[ri + rgl - 1].until : left[0].from;
|
2022-09-12 15:57:13 +02:00
|
|
|
|
|
|
|
left.splice(li, 0, {
|
2022-11-18 15:29:24 +01:00
|
|
|
from: rightStartTime,
|
2022-09-12 15:57:13 +02:00
|
|
|
until: ends,
|
2022-10-12 20:58:10 +02:00
|
|
|
group: right[ri].group,
|
2022-09-12 15:57:13 +02:00
|
|
|
});
|
2022-11-18 15:29:24 +01:00
|
|
|
lgl++;
|
2022-09-12 15:57:13 +02:00
|
|
|
|
2022-11-18 15:29:24 +01:00
|
|
|
timeCut = rightStartTime;
|
2022-09-12 15:57:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//check which ends sooner, add gap so both list ends at the same time
|
|
|
|
// here both list are non empty
|
2022-11-18 15:29:24 +01:00
|
|
|
const leftEndTime: AbsoluteTime = left[li + lgl - 1].until;
|
|
|
|
const rightEndTime: AbsoluteTime = right[ri + rgl - 1].until;
|
2022-09-12 15:57:13 +02:00
|
|
|
|
2022-11-18 15:29:24 +01:00
|
|
|
if (AbsoluteTime.cmp(leftEndTime, rightEndTime) > 0) {
|
|
|
|
right.splice(ri + rgl, 0, {
|
|
|
|
from: rightEndTime,
|
|
|
|
until: leftEndTime,
|
2022-10-12 20:58:10 +02:00
|
|
|
group: left[0].group,
|
2022-09-12 15:57:13 +02:00
|
|
|
});
|
2022-11-18 15:29:24 +01:00
|
|
|
rgl++;
|
2022-09-12 15:57:13 +02:00
|
|
|
}
|
2022-11-18 15:29:24 +01:00
|
|
|
if (AbsoluteTime.cmp(leftEndTime, rightEndTime) < 0) {
|
|
|
|
left.splice(li + lgl, 0, {
|
|
|
|
from: leftEndTime,
|
|
|
|
until: rightEndTime,
|
2022-10-12 20:58:10 +02:00
|
|
|
group: right[0].group,
|
2022-09-12 15:57:13 +02:00
|
|
|
});
|
2022-11-18 15:29:24 +01:00
|
|
|
lgl++;
|
2022-09-12 15:57:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//now both lists are non empty and (starts,ends) at the same time
|
2022-11-22 19:15:40 +01:00
|
|
|
while (li < lgs + lgl && ri < rgs + rgl) {
|
2022-09-16 19:27:24 +02:00
|
|
|
if (
|
|
|
|
AbsoluteTime.cmp(left[li].from, timeCut) !== 0 &&
|
|
|
|
AbsoluteTime.cmp(right[ri].from, timeCut) !== 0
|
|
|
|
) {
|
2022-09-12 15:57:13 +02:00
|
|
|
// timeCut comes from the latest "until" (expiration from the previous)
|
|
|
|
// and this value comes from the latest left or right
|
|
|
|
// it should be the same as the "from" from one of the latest left or right
|
|
|
|
// otherwise it means that there is missing a gap object in the middle
|
|
|
|
// the list is not complete and the behavior is undefined
|
2022-09-16 19:27:24 +02:00
|
|
|
throw Error(
|
|
|
|
"one of the list is not completed: list[i].until !== list[i+1].from",
|
|
|
|
);
|
2022-09-12 15:57:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pairList.push({
|
|
|
|
left: left[li].fee,
|
|
|
|
right: right[ri].fee,
|
|
|
|
from: timeCut,
|
|
|
|
until: AbsoluteTime.never(),
|
2022-10-12 20:58:10 +02:00
|
|
|
group: currentGroup,
|
2022-09-12 15:57:13 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
if (left[li].until.t_ms === right[ri].until.t_ms) {
|
|
|
|
timeCut = left[li].until;
|
|
|
|
ri++;
|
|
|
|
li++;
|
|
|
|
} else if (left[li].until.t_ms < right[ri].until.t_ms) {
|
|
|
|
timeCut = left[li].until;
|
|
|
|
li++;
|
|
|
|
} else if (left[li].until.t_ms > right[ri].until.t_ms) {
|
|
|
|
timeCut = right[ri].until;
|
|
|
|
ri++;
|
|
|
|
}
|
2022-09-16 19:27:24 +02:00
|
|
|
pairList[pairList.length - 1].until = timeCut;
|
2022-09-12 15:57:13 +02:00
|
|
|
|
2022-11-18 15:29:24 +01:00
|
|
|
// if (
|
|
|
|
// (li < left.length && left[li].group !== currentGroup) ||
|
|
|
|
// (ri < right.length && right[ri].group !== currentGroup)
|
|
|
|
// ) {
|
|
|
|
// //value changed, should break
|
|
|
|
// //this if will catch when both (left and right) change at the same time
|
|
|
|
// //if just one side changed it will catch in the while condition
|
|
|
|
// break;
|
|
|
|
// }
|
2022-09-12 15:57:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
//one of the list left or right can still have elements
|
|
|
|
if (li < left.length) {
|
2022-09-16 19:27:24 +02:00
|
|
|
let timeCut =
|
|
|
|
pairList.length > 0 &&
|
2022-11-22 19:15:40 +01:00
|
|
|
pairList[pairList.length - 1].group === left[li].group
|
2022-09-16 19:27:24 +02:00
|
|
|
? pairList[pairList.length - 1].until
|
|
|
|
: left[li].from;
|
2022-09-12 15:57:13 +02:00
|
|
|
while (li < left.length) {
|
|
|
|
pairList.push({
|
|
|
|
left: left[li].fee,
|
|
|
|
right: undefined,
|
|
|
|
from: timeCut,
|
|
|
|
until: left[li].until,
|
2022-10-12 20:58:10 +02:00
|
|
|
group: left[li].group,
|
2022-09-16 19:27:24 +02:00
|
|
|
});
|
|
|
|
timeCut = left[li].until;
|
2022-09-12 15:57:13 +02:00
|
|
|
li++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ri < right.length) {
|
2022-09-16 19:27:24 +02:00
|
|
|
let timeCut =
|
|
|
|
pairList.length > 0 &&
|
2022-11-22 19:15:40 +01:00
|
|
|
pairList[pairList.length - 1].group === right[ri].group
|
2022-09-16 19:27:24 +02:00
|
|
|
? pairList[pairList.length - 1].until
|
|
|
|
: right[ri].from;
|
2022-09-12 15:57:13 +02:00
|
|
|
while (ri < right.length) {
|
|
|
|
pairList.push({
|
|
|
|
right: right[ri].fee,
|
|
|
|
left: undefined,
|
|
|
|
from: timeCut,
|
|
|
|
until: right[ri].until,
|
2022-10-12 20:58:10 +02:00
|
|
|
group: right[ri].group,
|
2022-09-16 19:27:24 +02:00
|
|
|
});
|
|
|
|
timeCut = right[ri].until;
|
2022-09-12 15:57:13 +02:00
|
|
|
ri++;
|
|
|
|
}
|
|
|
|
}
|
2022-09-16 19:27:24 +02:00
|
|
|
return pairList;
|
2022-09-12 15:57:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-10-12 20:58:10 +02:00
|
|
|
* Create a usage timeline with the entity given.
|
2022-09-12 15:57:13 +02:00
|
|
|
*
|
2022-10-12 20:58:10 +02:00
|
|
|
* 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
|
2022-09-12 15:57:13 +02:00
|
|
|
*
|
2022-10-12 20:58:10 +02:00
|
|
|
* @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
|
2022-09-12 15:57:13 +02:00
|
|
|
* @param feeProp property of the element of the list that will be used as fee reference
|
2022-10-12 20:58:10 +02:00
|
|
|
* @param groupProp property of the element of the list that will be used for grouping
|
2022-09-12 15:57:13 +02:00
|
|
|
* @returns list of @type {FeeDescription} sorted by usage period
|
|
|
|
*/
|
2022-10-12 20:58:10 +02:00
|
|
|
export function createTimeline<Type extends object>(
|
|
|
|
list: Type[],
|
|
|
|
idProp: PropsWithReturnType<Type, string>,
|
|
|
|
periodStartProp: PropsWithReturnType<Type, TalerProtocolTimestamp>,
|
|
|
|
periodEndProp: PropsWithReturnType<Type, TalerProtocolTimestamp>,
|
2022-11-02 17:42:14 +01:00
|
|
|
feeProp: PropsWithReturnType<Type, AmountString>,
|
2022-10-12 20:58:10 +02:00
|
|
|
groupProp: PropsWithReturnType<Type, string> | undefined,
|
|
|
|
selectBestForOverlapping: (l: Type[]) => Type | undefined,
|
2022-09-12 15:57:13 +02:00
|
|
|
): FeeDescription[] {
|
2022-10-12 20:58:10 +02:00
|
|
|
/**
|
|
|
|
* First we create a list with with point in the timeline sorted
|
|
|
|
* by time and categorized by starting or ending.
|
|
|
|
*/
|
|
|
|
const sortedPointsInTime = list
|
2022-09-12 15:57:13 +02:00
|
|
|
.reduce((ps, denom) => {
|
|
|
|
//exclude denoms with bad configuration
|
2022-10-12 20:58:10 +02:00
|
|
|
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`);
|
2022-09-12 15:57:13 +02:00
|
|
|
}
|
|
|
|
ps.push({
|
|
|
|
type: "start",
|
2022-11-02 17:42:14 +01:00
|
|
|
fee: Amounts.stringify(fee),
|
2022-10-12 20:58:10 +02:00
|
|
|
group,
|
|
|
|
id,
|
|
|
|
moment: AbsoluteTime.fromTimestamp(stampStart),
|
2022-09-12 15:57:13 +02:00
|
|
|
denom,
|
|
|
|
});
|
|
|
|
ps.push({
|
|
|
|
type: "end",
|
2022-11-02 17:42:14 +01:00
|
|
|
fee: Amounts.stringify(fee),
|
2022-10-12 20:58:10 +02:00
|
|
|
group,
|
|
|
|
id,
|
|
|
|
moment: AbsoluteTime.fromTimestamp(stampEnd),
|
2022-09-12 15:57:13 +02:00
|
|
|
denom,
|
|
|
|
});
|
|
|
|
return ps;
|
2022-10-12 20:58:10 +02:00
|
|
|
}, [] as TimePoint<Type>[])
|
2022-09-12 15:57:13 +02:00
|
|
|
.sort((a, b) => {
|
2022-10-12 20:58:10 +02:00
|
|
|
const v = a.group == b.group ? 0 : a.group > b.group ? 1 : -1;
|
2022-09-12 15:57:13 +02:00
|
|
|
if (v != 0) return v;
|
|
|
|
const t = AbsoluteTime.cmp(a.moment, b.moment);
|
|
|
|
if (t != 0) return t;
|
|
|
|
if (a.type === b.type) return 0;
|
|
|
|
return a.type === "start" ? 1 : -1;
|
|
|
|
});
|
|
|
|
|
2022-10-12 20:58:10 +02:00
|
|
|
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.
|
|
|
|
*/
|
2022-09-12 15:57:13 +02:00
|
|
|
let prev = result.length > 0 ? result[result.length - 1] : undefined;
|
2022-10-12 20:58:10 +02:00
|
|
|
const prevHasSameValue = prev && prev.group == cursor.group;
|
2022-09-12 15:57:13 +02:00
|
|
|
if (prev) {
|
|
|
|
if (prevHasSameValue) {
|
|
|
|
prev.until = cursor.moment;
|
|
|
|
|
|
|
|
if (prev.from.t_ms === prev.until.t_ms) {
|
|
|
|
result.pop();
|
|
|
|
prev = result[result.length - 1];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// the last end adds a gap that we have to remove
|
|
|
|
result.pop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-12 20:58:10 +02:00
|
|
|
/**
|
|
|
|
* With the current moment in the iteration we
|
|
|
|
* should keep updated which entities are current
|
|
|
|
* active in this period of time.
|
|
|
|
*/
|
2022-09-12 15:57:13 +02:00
|
|
|
if (cursor.type === "end") {
|
2022-10-12 20:58:10 +02:00
|
|
|
const loc = activeAtTheSameTime.findIndex((v) => v[idProp] === cursor.id);
|
2022-09-12 15:57:13 +02:00
|
|
|
if (loc === -1) {
|
2022-10-12 20:58:10 +02:00
|
|
|
throw Error(`denomination ${cursor.id} has an end but no start`);
|
2022-09-12 15:57:13 +02:00
|
|
|
}
|
|
|
|
activeAtTheSameTime.splice(loc, 1);
|
|
|
|
} else if (cursor.type === "start") {
|
|
|
|
activeAtTheSameTime.push(cursor.denom);
|
|
|
|
} else {
|
|
|
|
const exhaustiveCheck: never = cursor.type;
|
|
|
|
throw new Error(`not TimePoint defined for type: ${exhaustiveCheck}`);
|
|
|
|
}
|
|
|
|
|
2022-10-12 20:58:10 +02:00
|
|
|
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
|
|
|
|
*/
|
2022-09-12 15:57:13 +02:00
|
|
|
if (cursor.type !== "end") {
|
|
|
|
throw Error(
|
2022-10-12 20:58:10 +02:00
|
|
|
`denomination ${cursor.id} starts after ending or doesn't have an ending`,
|
2022-09-12 15:57:13 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
if (activeAtTheSameTime.length > 0) {
|
|
|
|
throw Error(
|
|
|
|
`there are ${activeAtTheSameTime.length} denominations without ending`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-10-12 20:58:10 +02:00
|
|
|
const current = selectBestForOverlapping(activeAtTheSameTime);
|
2022-09-12 15:57:13 +02:00
|
|
|
|
|
|
|
if (current) {
|
2022-10-12 20:58:10 +02:00
|
|
|
/**
|
|
|
|
* 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;
|
2022-09-12 15:57:13 +02:00
|
|
|
if (
|
|
|
|
prev === undefined || //is the first
|
|
|
|
!prev.fee || //is a gap
|
2022-10-12 20:58:10 +02:00
|
|
|
Amounts.cmp(prev.fee, currentFee) !== 0 // prev has different fee
|
2022-09-12 15:57:13 +02:00
|
|
|
) {
|
|
|
|
result.push({
|
2022-10-12 20:58:10 +02:00
|
|
|
group: cursor.group,
|
2022-09-12 15:57:13 +02:00
|
|
|
from: cursor.moment,
|
|
|
|
until: AbsoluteTime.never(), //not yet known
|
2022-11-02 17:42:14 +01:00
|
|
|
fee: Amounts.stringify(currentFee),
|
2022-09-12 15:57:13 +02:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
prev.until = cursor.moment;
|
|
|
|
}
|
|
|
|
} else {
|
2022-10-12 20:58:10 +02:00
|
|
|
/**
|
|
|
|
* No active element in this period of time, so we add a gap (no fee)
|
|
|
|
* Next element in the list will defined the ending.
|
|
|
|
*/
|
2022-09-12 15:57:13 +02:00
|
|
|
result.push({
|
2022-10-12 20:58:10 +02:00
|
|
|
group: cursor.group,
|
2022-09-12 15:57:13 +02:00
|
|
|
from: cursor.moment,
|
|
|
|
until: AbsoluteTime.never(), //not yet known
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}, [] as FeeDescription[]);
|
|
|
|
}
|