wallet-core: towards faster coin selection
This commit is contained in:
parent
5d08379139
commit
b7f7b95602
@ -694,6 +694,7 @@ const codecForDenominationInfo = (): Codec<DenominationInfo> =>
|
|||||||
.property("stampExpireWithdraw", codecForTimestamp)
|
.property("stampExpireWithdraw", codecForTimestamp)
|
||||||
.property("stampExpireLegal", codecForTimestamp)
|
.property("stampExpireLegal", codecForTimestamp)
|
||||||
.property("stampExpireDeposit", codecForTimestamp)
|
.property("stampExpireDeposit", codecForTimestamp)
|
||||||
|
.property("exchangeBaseUrl", codecForString())
|
||||||
.build("codecForDenominationInfo");
|
.build("codecForDenominationInfo");
|
||||||
|
|
||||||
export interface DenominationInfo {
|
export interface DenominationInfo {
|
||||||
@ -747,6 +748,8 @@ export interface DenominationInfo {
|
|||||||
* Data after which coins of this denomination can't be deposited anymore.
|
* Data after which coins of this denomination can't be deposited anymore.
|
||||||
*/
|
*/
|
||||||
stampExpireDeposit: TalerProtocolTimestamp;
|
stampExpireDeposit: TalerProtocolTimestamp;
|
||||||
|
|
||||||
|
exchangeBaseUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Operation = "deposit" | "withdraw" | "refresh" | "refund";
|
export type Operation = "deposit" | "withdraw" | "refresh" | "refund";
|
||||||
|
@ -348,6 +348,7 @@ export namespace DenominationRecord {
|
|||||||
stampExpireWithdraw: d.stampExpireWithdraw,
|
stampExpireWithdraw: d.stampExpireWithdraw,
|
||||||
stampStart: d.stampStart,
|
stampStart: d.stampStart,
|
||||||
value: DenominationRecord.getValue(d),
|
value: DenominationRecord.getValue(d),
|
||||||
|
exchangeBaseUrl: d.exchangeBaseUrl,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1778,6 +1779,10 @@ export const WalletStoresV1 = {
|
|||||||
{
|
{
|
||||||
byBaseUrl: describeIndex("byBaseUrl", "exchangeBaseUrl"),
|
byBaseUrl: describeIndex("byBaseUrl", "exchangeBaseUrl"),
|
||||||
byDenomPubHash: describeIndex("byDenomPubHash", "denomPubHash"),
|
byDenomPubHash: describeIndex("byDenomPubHash", "denomPubHash"),
|
||||||
|
byDenomPubHashAndStatus: describeIndex("byDenomPubHashAndStatus", [
|
||||||
|
"denomPubHash",
|
||||||
|
"status",
|
||||||
|
]),
|
||||||
byCoinEvHash: describeIndex("byCoinEvHash", "coinEvHash"),
|
byCoinEvHash: describeIndex("byCoinEvHash", "coinEvHash"),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -51,7 +51,7 @@ import {
|
|||||||
OperationStatus,
|
OperationStatus,
|
||||||
} from "../db.js";
|
} from "../db.js";
|
||||||
import { InternalWalletState } from "../internal-wallet-state.js";
|
import { InternalWalletState } from "../internal-wallet-state.js";
|
||||||
import { selectPayCoins } from "../util/coinSelection.js";
|
import { selectPayCoinsLegacy } from "../util/coinSelection.js";
|
||||||
import { readSuccessResponseJsonOrThrow } from "../util/http.js";
|
import { readSuccessResponseJsonOrThrow } from "../util/http.js";
|
||||||
import { spendCoins } from "../wallet.js";
|
import { spendCoins } from "../wallet.js";
|
||||||
import { getExchangeDetails } from "./exchanges.js";
|
import { getExchangeDetails } from "./exchanges.js";
|
||||||
@ -271,7 +271,7 @@ export async function getFeeForDeposit(
|
|||||||
|
|
||||||
const candidates = await getCandidatePayCoins(ws, csr);
|
const candidates = await getCandidatePayCoins(ws, csr);
|
||||||
|
|
||||||
const payCoinSel = selectPayCoins({
|
const payCoinSel = selectPayCoinsLegacy({
|
||||||
candidates,
|
candidates,
|
||||||
contractTermsAmount: csr.amount,
|
contractTermsAmount: csr.amount,
|
||||||
depositFeeLimit: csr.maxDepositFee,
|
depositFeeLimit: csr.maxDepositFee,
|
||||||
@ -367,7 +367,7 @@ export async function prepareDepositGroup(
|
|||||||
wireMethod: contractData.wireMethod,
|
wireMethod: contractData.wireMethod,
|
||||||
});
|
});
|
||||||
|
|
||||||
const payCoinSel = selectPayCoins({
|
const payCoinSel = selectPayCoinsLegacy({
|
||||||
candidates,
|
candidates,
|
||||||
contractTermsAmount: contractData.amount,
|
contractTermsAmount: contractData.amount,
|
||||||
depositFeeLimit: contractData.maxDepositFee,
|
depositFeeLimit: contractData.maxDepositFee,
|
||||||
@ -470,7 +470,7 @@ export async function createDepositGroup(
|
|||||||
wireMethod: contractData.wireMethod,
|
wireMethod: contractData.wireMethod,
|
||||||
});
|
});
|
||||||
|
|
||||||
const payCoinSel = selectPayCoins({
|
const payCoinSel = selectPayCoinsLegacy({
|
||||||
candidates,
|
candidates,
|
||||||
contractTermsAmount: contractData.amount,
|
contractTermsAmount: contractData.amount,
|
||||||
depositFeeLimit: contractData.maxDepositFee,
|
depositFeeLimit: contractData.maxDepositFee,
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
AbsoluteTime,
|
AbsoluteTime,
|
||||||
|
AgeRestriction,
|
||||||
AmountJson,
|
AmountJson,
|
||||||
Amounts,
|
Amounts,
|
||||||
codecForContractTerms,
|
codecForContractTerms,
|
||||||
@ -36,6 +37,8 @@ import {
|
|||||||
ConfirmPayResultType,
|
ConfirmPayResultType,
|
||||||
ContractTerms,
|
ContractTerms,
|
||||||
ContractTermsUtil,
|
ContractTermsUtil,
|
||||||
|
DenominationInfo,
|
||||||
|
DenominationPubKey,
|
||||||
Duration,
|
Duration,
|
||||||
encodeCrock,
|
encodeCrock,
|
||||||
ForcedCoinSel,
|
ForcedCoinSel,
|
||||||
@ -50,6 +53,7 @@ import {
|
|||||||
PreparePayResult,
|
PreparePayResult,
|
||||||
PreparePayResultType,
|
PreparePayResultType,
|
||||||
RefreshReason,
|
RefreshReason,
|
||||||
|
strcmp,
|
||||||
TalerErrorCode,
|
TalerErrorCode,
|
||||||
TalerErrorDetail,
|
TalerErrorDetail,
|
||||||
TalerProtocolTimestamp,
|
TalerProtocolTimestamp,
|
||||||
@ -86,9 +90,11 @@ import { assertUnreachable } from "../util/assertUnreachable.js";
|
|||||||
import {
|
import {
|
||||||
AvailableCoinInfo,
|
AvailableCoinInfo,
|
||||||
CoinCandidateSelection,
|
CoinCandidateSelection,
|
||||||
|
CoinSelectionTally,
|
||||||
PreviousPayCoins,
|
PreviousPayCoins,
|
||||||
selectForcedPayCoins,
|
selectForcedPayCoins,
|
||||||
selectPayCoins,
|
selectPayCoinsLegacy,
|
||||||
|
tallyFees,
|
||||||
} from "../util/coinSelection.js";
|
} from "../util/coinSelection.js";
|
||||||
import {
|
import {
|
||||||
getHttpResponseErrorDetails,
|
getHttpResponseErrorDetails,
|
||||||
@ -962,7 +968,7 @@ async function handleInsufficientFunds(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = selectPayCoins({
|
const res = selectPayCoinsLegacy({
|
||||||
candidates,
|
candidates,
|
||||||
contractTermsAmount: contractData.amount,
|
contractTermsAmount: contractData.amount,
|
||||||
depositFeeLimit: contractData.maxDepositFee,
|
depositFeeLimit: contractData.maxDepositFee,
|
||||||
@ -1019,6 +1025,205 @@ async function unblockBackup(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SelectPayCoinRequestNg {
|
||||||
|
exchanges: string[];
|
||||||
|
auditors: string[];
|
||||||
|
wireMethod: string;
|
||||||
|
contractTermsAmount: AmountJson;
|
||||||
|
depositFeeLimit: AmountJson;
|
||||||
|
wireFeeLimit: AmountJson;
|
||||||
|
wireFeeAmortization: number;
|
||||||
|
prevPayCoins?: PreviousPayCoins;
|
||||||
|
requiredMinimumAge?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AvailableDenom = DenominationInfo & {
|
||||||
|
numAvailable: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a list of candidate coins, select coins to spend under the merchant's
|
||||||
|
* constraints.
|
||||||
|
*
|
||||||
|
* The prevPayCoins can be specified to "repair" a coin selection
|
||||||
|
* by adding additional coins, after a broken (e.g. double-spent) coin
|
||||||
|
* has been removed from the selection.
|
||||||
|
*
|
||||||
|
* This function is only exported for the sake of unit tests.
|
||||||
|
*/
|
||||||
|
export async function selectPayCoinsNew(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
req: SelectPayCoinRequestNg,
|
||||||
|
): Promise<PayCoinSelection | undefined> {
|
||||||
|
const {
|
||||||
|
contractTermsAmount,
|
||||||
|
depositFeeLimit,
|
||||||
|
wireFeeLimit,
|
||||||
|
wireFeeAmortization,
|
||||||
|
} = req;
|
||||||
|
|
||||||
|
const [candidateDenoms, wireFeesPerExchange] = await ws.db
|
||||||
|
.mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations])
|
||||||
|
.runReadOnly(async (tx) => {
|
||||||
|
const denoms: AvailableDenom[] = [];
|
||||||
|
const exchanges = await tx.exchanges.iter().toArray();
|
||||||
|
const wfPerExchange: Record<string, AmountJson> = {};
|
||||||
|
for (const exchange of exchanges) {
|
||||||
|
const exchangeDetails = await getExchangeDetails(tx, exchange.baseUrl);
|
||||||
|
if (exchangeDetails?.currency !== contractTermsAmount.currency) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let accepted = false;
|
||||||
|
if (req.exchanges.includes(exchange.baseUrl)) {
|
||||||
|
accepted = true;
|
||||||
|
} else {
|
||||||
|
for (const auditor of exchangeDetails.auditors) {
|
||||||
|
if (req.auditors.includes(auditor.auditor_url)) {
|
||||||
|
accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!accepted) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// FIXME: Do this query more efficiently via indexing
|
||||||
|
const exchangeDenoms = await tx.denominations.indexes.byExchangeBaseUrl
|
||||||
|
.iter(exchangeDetails.exchangeBaseUrl)
|
||||||
|
.filter((x) => x.freshCoinCount != null && x.freshCoinCount > 0);
|
||||||
|
// FIXME: Should we exclude denominations that are
|
||||||
|
// not spendable anymore?
|
||||||
|
for (const denom of exchangeDenoms) {
|
||||||
|
denoms.push({
|
||||||
|
...DenominationRecord.toDenomInfo(denom),
|
||||||
|
numAvailable: denom.freshCoinCount ?? 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [denoms, wfPerExchange];
|
||||||
|
});
|
||||||
|
|
||||||
|
const coinPubs: string[] = [];
|
||||||
|
const coinContributions: AmountJson[] = [];
|
||||||
|
const currency = contractTermsAmount.currency;
|
||||||
|
|
||||||
|
let tally: CoinSelectionTally = {
|
||||||
|
amountPayRemaining: contractTermsAmount,
|
||||||
|
amountWireFeeLimitRemaining: wireFeeLimit,
|
||||||
|
amountDepositFeeLimitRemaining: depositFeeLimit,
|
||||||
|
customerDepositFees: Amounts.getZero(currency),
|
||||||
|
customerWireFees: Amounts.getZero(currency),
|
||||||
|
wireFeeCoveredForExchange: new Set(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const prevPayCoins = req.prevPayCoins ?? [];
|
||||||
|
|
||||||
|
// Look at existing pay coin selection and tally up
|
||||||
|
for (const prev of prevPayCoins) {
|
||||||
|
tally = tallyFees(
|
||||||
|
tally,
|
||||||
|
wireFeesPerExchange,
|
||||||
|
wireFeeAmortization,
|
||||||
|
prev.exchangeBaseUrl,
|
||||||
|
prev.feeDeposit,
|
||||||
|
);
|
||||||
|
tally.amountPayRemaining = Amounts.sub(
|
||||||
|
tally.amountPayRemaining,
|
||||||
|
prev.contribution,
|
||||||
|
).amount;
|
||||||
|
|
||||||
|
coinPubs.push(prev.coinPub);
|
||||||
|
coinContributions.push(prev.contribution);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by available amount (descending), deposit fee (ascending) and
|
||||||
|
// denomPub (ascending) if deposit fee is the same
|
||||||
|
// (to guarantee deterministic results)
|
||||||
|
candidateDenoms.sort(
|
||||||
|
(o1, o2) =>
|
||||||
|
-Amounts.cmp(o1.value, o2.value) ||
|
||||||
|
Amounts.cmp(o1.feeDeposit, o2.feeDeposit) ||
|
||||||
|
strcmp(o1.denomPubHash, o2.denomPubHash),
|
||||||
|
);
|
||||||
|
|
||||||
|
// FIXME: Here, we should select coins in a smarter way.
|
||||||
|
// Instead of always spending the next-largest coin,
|
||||||
|
// we should try to find the smallest coin that covers the
|
||||||
|
// amount.
|
||||||
|
|
||||||
|
const selectedDenom: {
|
||||||
|
[dph: string]: AmountJson[];
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
for (const aci of candidateDenoms) {
|
||||||
|
const contributions: AmountJson[] = [];
|
||||||
|
for (let i = 0; i < aci.numAvailable; i++) {
|
||||||
|
// Don't use this coin if depositing it is more expensive than
|
||||||
|
// the amount it would give the merchant.
|
||||||
|
if (Amounts.cmp(aci.feeDeposit, aci.value) > 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Amounts.isZero(tally.amountPayRemaining)) {
|
||||||
|
// We have spent enough!
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tally = tallyFees(
|
||||||
|
tally,
|
||||||
|
wireFeesPerExchange,
|
||||||
|
wireFeeAmortization,
|
||||||
|
aci.exchangeBaseUrl,
|
||||||
|
aci.feeDeposit,
|
||||||
|
);
|
||||||
|
|
||||||
|
let coinSpend = Amounts.max(
|
||||||
|
Amounts.min(tally.amountPayRemaining, aci.value),
|
||||||
|
aci.feeDeposit,
|
||||||
|
);
|
||||||
|
|
||||||
|
tally.amountPayRemaining = Amounts.sub(
|
||||||
|
tally.amountPayRemaining,
|
||||||
|
coinSpend,
|
||||||
|
).amount;
|
||||||
|
contributions.push(coinSpend);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contributions.length) {
|
||||||
|
selectedDenom[aci.denomPubHash] = contributions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Amounts.isZero(tally.amountPayRemaining)) {
|
||||||
|
await ws.db
|
||||||
|
.mktx((x) => [x.coins, x.denominations])
|
||||||
|
.runReadOnly(async (tx) => {
|
||||||
|
for (const dph of Object.keys(selectedDenom)) {
|
||||||
|
const contributions = selectedDenom[dph];
|
||||||
|
const coins = await tx.coins.indexes.byDenomPubHashAndStatus.getAll(
|
||||||
|
[dph, CoinStatus.Fresh],
|
||||||
|
contributions.length,
|
||||||
|
);
|
||||||
|
if (coins.length != contributions.length) {
|
||||||
|
throw Error(
|
||||||
|
`coin selection failed (not available anymore, got only ${coins.length}/${contributions.length})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
coinPubs.push(...coins.map((x) => x.coinPub));
|
||||||
|
coinContributions.push(...contributions);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
paymentAmount: contractTermsAmount,
|
||||||
|
coinContributions,
|
||||||
|
coinPubs,
|
||||||
|
customerDepositFees: tally.customerDepositFees,
|
||||||
|
customerWireFees: tally.customerWireFees,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export async function checkPaymentByProposalId(
|
export async function checkPaymentByProposalId(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
proposalId: string,
|
proposalId: string,
|
||||||
@ -1079,7 +1284,7 @@ export async function checkPaymentByProposalId(
|
|||||||
wireFeeAmortization: contractData.wireFeeAmortization,
|
wireFeeAmortization: contractData.wireFeeAmortization,
|
||||||
wireMethod: contractData.wireMethod,
|
wireMethod: contractData.wireMethod,
|
||||||
});
|
});
|
||||||
const res = selectPayCoins({
|
const res = selectPayCoinsLegacy({
|
||||||
candidates,
|
candidates,
|
||||||
contractTermsAmount: contractData.amount,
|
contractTermsAmount: contractData.amount,
|
||||||
depositFeeLimit: contractData.maxDepositFee,
|
depositFeeLimit: contractData.maxDepositFee,
|
||||||
@ -1408,7 +1613,7 @@ export async function confirmPay(
|
|||||||
requiredMinimumAge: contractData.minimumAge,
|
requiredMinimumAge: contractData.minimumAge,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
res = selectPayCoins({
|
res = selectPayCoinsLegacy({
|
||||||
candidates,
|
candidates,
|
||||||
contractTermsAmount: contractData.amount,
|
contractTermsAmount: contractData.amount,
|
||||||
depositFeeLimit: contractData.maxDepositFee,
|
depositFeeLimit: contractData.maxDepositFee,
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
import test from "ava";
|
import test from "ava";
|
||||||
import { AmountJson, Amounts, DenomKeyType } from "@gnu-taler/taler-util";
|
import { AmountJson, Amounts, DenomKeyType } from "@gnu-taler/taler-util";
|
||||||
import { AvailableCoinInfo, selectPayCoins } from "./coinSelection.js";
|
import { AvailableCoinInfo, selectPayCoinsLegacy } from "./coinSelection.js";
|
||||||
|
|
||||||
function a(x: string): AmountJson {
|
function a(x: string): AmountJson {
|
||||||
const amt = Amounts.parse(x);
|
const amt = Amounts.parse(x);
|
||||||
@ -66,7 +66,7 @@ test("it should be able to pay if merchant takes the fees", (t) => {
|
|||||||
];
|
];
|
||||||
acis.forEach((x, i) => (x.coinPub = String(i)));
|
acis.forEach((x, i) => (x.coinPub = String(i)));
|
||||||
|
|
||||||
const res = selectPayCoins({
|
const res = selectPayCoinsLegacy({
|
||||||
candidates: {
|
candidates: {
|
||||||
candidateCoins: acis,
|
candidateCoins: acis,
|
||||||
wireFeesPerExchange: {},
|
wireFeesPerExchange: {},
|
||||||
@ -94,7 +94,7 @@ test("it should take the last two coins if it pays less fees", (t) => {
|
|||||||
];
|
];
|
||||||
acis.forEach((x, i) => (x.coinPub = String(i)));
|
acis.forEach((x, i) => (x.coinPub = String(i)));
|
||||||
|
|
||||||
const res = selectPayCoins({
|
const res = selectPayCoinsLegacy({
|
||||||
candidates: {
|
candidates: {
|
||||||
candidateCoins: acis,
|
candidateCoins: acis,
|
||||||
wireFeesPerExchange: {},
|
wireFeesPerExchange: {},
|
||||||
@ -122,7 +122,7 @@ test("it should take the last coins if the merchant doest not take all the fee",
|
|||||||
];
|
];
|
||||||
acis.forEach((x, i) => (x.coinPub = String(i)));
|
acis.forEach((x, i) => (x.coinPub = String(i)));
|
||||||
|
|
||||||
const res = selectPayCoins({
|
const res = selectPayCoinsLegacy({
|
||||||
candidates: {
|
candidates: {
|
||||||
candidateCoins: acis,
|
candidateCoins: acis,
|
||||||
wireFeesPerExchange: {},
|
wireFeesPerExchange: {},
|
||||||
@ -148,7 +148,7 @@ test("it should use 3 coins to cover fees and payment", (t) => {
|
|||||||
fakeAci("EUR:1.0", "EUR:0.5"), //contributed value .5
|
fakeAci("EUR:1.0", "EUR:0.5"), //contributed value .5
|
||||||
];
|
];
|
||||||
|
|
||||||
const res = selectPayCoins({
|
const res = selectPayCoinsLegacy({
|
||||||
candidates: {
|
candidates: {
|
||||||
candidateCoins: acis,
|
candidateCoins: acis,
|
||||||
wireFeesPerExchange: {},
|
wireFeesPerExchange: {},
|
||||||
@ -174,7 +174,7 @@ test("it should return undefined if there is not enough coins", (t) => {
|
|||||||
fakeAci("EUR:1.0", "EUR:0.5"),
|
fakeAci("EUR:1.0", "EUR:0.5"),
|
||||||
];
|
];
|
||||||
|
|
||||||
const res = selectPayCoins({
|
const res = selectPayCoinsLegacy({
|
||||||
candidates: {
|
candidates: {
|
||||||
candidateCoins: acis,
|
candidateCoins: acis,
|
||||||
wireFeesPerExchange: {},
|
wireFeesPerExchange: {},
|
||||||
@ -194,7 +194,7 @@ test("it should return undefined if there is not enough coins (taking into accou
|
|||||||
fakeAci("EUR:1.0", "EUR:0.5"),
|
fakeAci("EUR:1.0", "EUR:0.5"),
|
||||||
fakeAci("EUR:1.0", "EUR:0.5"),
|
fakeAci("EUR:1.0", "EUR:0.5"),
|
||||||
];
|
];
|
||||||
const res = selectPayCoins({
|
const res = selectPayCoinsLegacy({
|
||||||
candidates: {
|
candidates: {
|
||||||
candidateCoins: acis,
|
candidateCoins: acis,
|
||||||
wireFeesPerExchange: {},
|
wireFeesPerExchange: {},
|
||||||
@ -213,7 +213,7 @@ test("it should not count into customer fee if merchant can afford it", (t) => {
|
|||||||
fakeAci("EUR:1.0", "EUR:0.1"),
|
fakeAci("EUR:1.0", "EUR:0.1"),
|
||||||
fakeAci("EUR:1.0", "EUR:0.1"),
|
fakeAci("EUR:1.0", "EUR:0.1"),
|
||||||
];
|
];
|
||||||
const res = selectPayCoins({
|
const res = selectPayCoinsLegacy({
|
||||||
candidates: {
|
candidates: {
|
||||||
candidateCoins: acis,
|
candidateCoins: acis,
|
||||||
wireFeesPerExchange: {},
|
wireFeesPerExchange: {},
|
||||||
@ -240,7 +240,7 @@ test("it should use the coins that spent less relative fee", (t) => {
|
|||||||
];
|
];
|
||||||
acis.forEach((x, i) => (x.coinPub = String(i)));
|
acis.forEach((x, i) => (x.coinPub = String(i)));
|
||||||
|
|
||||||
const res = selectPayCoins({
|
const res = selectPayCoinsLegacy({
|
||||||
candidates: {
|
candidates: {
|
||||||
candidateCoins: acis,
|
candidateCoins: acis,
|
||||||
wireFeesPerExchange: {},
|
wireFeesPerExchange: {},
|
||||||
@ -263,7 +263,7 @@ test("coin selection 9", (t) => {
|
|||||||
fakeAci("EUR:1.0", "EUR:0.2"),
|
fakeAci("EUR:1.0", "EUR:0.2"),
|
||||||
fakeAci("EUR:0.2", "EUR:0.2"),
|
fakeAci("EUR:0.2", "EUR:0.2"),
|
||||||
];
|
];
|
||||||
const res = selectPayCoins({
|
const res = selectPayCoinsLegacy({
|
||||||
candidates: {
|
candidates: {
|
||||||
candidateCoins: acis,
|
candidateCoins: acis,
|
||||||
wireFeesPerExchange: {},
|
wireFeesPerExchange: {},
|
||||||
@ -290,7 +290,7 @@ test("it should be able to use unrestricted coins for age restricted contract",
|
|||||||
fakeAciWithAgeRestriction("EUR:1.0", "EUR:0.2"),
|
fakeAciWithAgeRestriction("EUR:1.0", "EUR:0.2"),
|
||||||
fakeAciWithAgeRestriction("EUR:0.2", "EUR:0.2"),
|
fakeAciWithAgeRestriction("EUR:0.2", "EUR:0.2"),
|
||||||
];
|
];
|
||||||
const res = selectPayCoins({
|
const res = selectPayCoinsLegacy({
|
||||||
candidates: {
|
candidates: {
|
||||||
candidateCoins: acis,
|
candidateCoins: acis,
|
||||||
wireFeesPerExchange: {},
|
wireFeesPerExchange: {},
|
||||||
|
@ -97,7 +97,7 @@ export interface SelectPayCoinRequest {
|
|||||||
requiredMinimumAge?: number;
|
requiredMinimumAge?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CoinSelectionTally {
|
export interface CoinSelectionTally {
|
||||||
/**
|
/**
|
||||||
* Amount that still needs to be paid.
|
* Amount that still needs to be paid.
|
||||||
* May increase during the computation when fees need to be covered.
|
* May increase during the computation when fees need to be covered.
|
||||||
@ -125,7 +125,7 @@ interface CoinSelectionTally {
|
|||||||
/**
|
/**
|
||||||
* Account for the fees of spending a coin.
|
* Account for the fees of spending a coin.
|
||||||
*/
|
*/
|
||||||
function tallyFees(
|
export function tallyFees(
|
||||||
tally: CoinSelectionTally,
|
tally: CoinSelectionTally,
|
||||||
wireFeesPerExchange: Record<string, AmountJson>,
|
wireFeesPerExchange: Record<string, AmountJson>,
|
||||||
wireFeeAmortization: number,
|
wireFeeAmortization: number,
|
||||||
@ -204,7 +204,7 @@ function tallyFees(
|
|||||||
*
|
*
|
||||||
* This function is only exported for the sake of unit tests.
|
* This function is only exported for the sake of unit tests.
|
||||||
*/
|
*/
|
||||||
export function selectPayCoins(
|
export function selectPayCoinsLegacy(
|
||||||
req: SelectPayCoinRequest,
|
req: SelectPayCoinRequest,
|
||||||
): PayCoinSelection | undefined {
|
): PayCoinSelection | undefined {
|
||||||
const {
|
const {
|
||||||
|
@ -743,19 +743,9 @@ async function getExchangeDetailedInfo(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const denominations: DenomInfo[] = denominationRecords.map((x) => ({
|
const denominations: DenomInfo[] = denominationRecords.map((x) =>
|
||||||
denomPub: x.denomPub,
|
DenominationRecord.toDenomInfo(x),
|
||||||
denomPubHash: x.denomPubHash,
|
);
|
||||||
feeDeposit: x.fees.feeDeposit,
|
|
||||||
feeRefresh: x.fees.feeRefresh,
|
|
||||||
feeRefund: x.fees.feeRefund,
|
|
||||||
feeWithdraw: x.fees.feeWithdraw,
|
|
||||||
stampExpireDeposit: x.stampExpireDeposit,
|
|
||||||
stampExpireLegal: x.stampExpireLegal,
|
|
||||||
stampExpireWithdraw: x.stampExpireWithdraw,
|
|
||||||
stampStart: x.stampStart,
|
|
||||||
value: DenominationRecord.getValue(x),
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
info: {
|
info: {
|
||||||
@ -1591,20 +1581,7 @@ class InternalWalletStateImpl implements InternalWalletState {
|
|||||||
}
|
}
|
||||||
const d = await tx.denominations.get([exchangeBaseUrl, denomPubHash]);
|
const d = await tx.denominations.get([exchangeBaseUrl, denomPubHash]);
|
||||||
if (d) {
|
if (d) {
|
||||||
const denomInfo = {
|
return DenominationRecord.toDenomInfo(d);
|
||||||
denomPub: d.denomPub,
|
|
||||||
denomPubHash: d.denomPubHash,
|
|
||||||
feeDeposit: d.fees.feeDeposit,
|
|
||||||
feeRefresh: d.fees.feeRefresh,
|
|
||||||
feeRefund: d.fees.feeRefund,
|
|
||||||
feeWithdraw: d.fees.feeWithdraw,
|
|
||||||
stampExpireDeposit: d.stampExpireDeposit,
|
|
||||||
stampExpireLegal: d.stampExpireLegal,
|
|
||||||
stampExpireWithdraw: d.stampExpireWithdraw,
|
|
||||||
stampStart: d.stampStart,
|
|
||||||
value: DenominationRecord.getValue(d),
|
|
||||||
};
|
|
||||||
return denomInfo;
|
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user