diff options
Diffstat (limited to 'packages/taler-wallet-core')
| -rw-r--r-- | packages/taler-wallet-core/package.json | 4 | ||||
| -rw-r--r-- | packages/taler-wallet-core/src/dbless.ts | 2 | ||||
| -rw-r--r-- | packages/taler-wallet-core/src/util/coinSelection.test.ts | 156 | ||||
| -rw-r--r-- | packages/taler-wallet-core/src/util/coinSelection.ts | 20 | 
4 files changed, 172 insertions, 10 deletions
| diff --git a/packages/taler-wallet-core/package.json b/packages/taler-wallet-core/package.json index fda7b6081..668fe4b92 100644 --- a/packages/taler-wallet-core/package.json +++ b/packages/taler-wallet-core/package.json @@ -15,9 +15,10 @@      "compile": "tsc",      "pretty": "prettier --write src",      "test": "tsc && ava", +    "typedoc": "typedoc --out dist/typedoc ./src/",      "coverage": "tsc && c8 --src src --all ava",      "coverage:html": "tsc && c8 -r html --src src --all ava", -    "clean": "rimraf dist lib tsconfig.tsbuildinfo" +    "clean": "rm -rf dist lib tsconfig.tsbuildinfo"    },    "files": [      "AUTHORS", @@ -64,7 +65,6 @@      "jed": "^1.1.1",      "po2json": "^0.4.5",      "prettier": "^2.8.8", -    "rimraf": "^3.0.2",      "typedoc": "^0.25.1",      "typescript": "^5.2.2"    }, diff --git a/packages/taler-wallet-core/src/dbless.ts b/packages/taler-wallet-core/src/dbless.ts index 65c293bdf..d70eab888 100644 --- a/packages/taler-wallet-core/src/dbless.ts +++ b/packages/taler-wallet-core/src/dbless.ts @@ -31,6 +31,7 @@ import {    AmountJson,    Amounts,    AmountString, +  BankAccessApiClient,    codecForAny,    codecForBankWithdrawalOperationPostResponse,    codecForBatchDepositSuccess, @@ -53,7 +54,6 @@ import {    HttpRequestLibrary,    readSuccessResponseJsonOrThrow,  } from "@gnu-taler/taler-util/http"; -import { BankAccessApiClient } from "../../taler-util/src/bank-api-client.js";  import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";  import { DenominationRecord } from "./db.js";  import { isWithdrawableDenom } from "./index.js"; diff --git a/packages/taler-wallet-core/src/util/coinSelection.test.ts b/packages/taler-wallet-core/src/util/coinSelection.test.ts index b907eb160..2a322c4a9 100644 --- a/packages/taler-wallet-core/src/util/coinSelection.test.ts +++ b/packages/taler-wallet-core/src/util/coinSelection.test.ts @@ -17,9 +17,165 @@ import {    AbsoluteTime,    AgeRestriction,    AmountJson, +  AmountString,    Amounts, +  DenomKeyType,    Duration,    TransactionAmountMode,  } 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), +    }, +  }; +} + +const inTheDistantFuture = AbsoluteTime.toProtocolTimestamp( +  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") +  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 +  ); + +  expect(t, coins).deep.equal({ +    "hash0;32;http://exchange.localhost/": { +      exchangeBaseUrl: "http://exchange.localhost/", +      denomPubHash: "hash0", +      maxAge: 32, +      contributions: [Amounts.parseOrThrow("LOCAL:2")], +    } +  }); + +  expect(t, tally).deep.equal({ +    amountAcc: Amounts.parseOrThrow("LOCAL:2"), +    depositFeesAcc: Amounts.parseOrThrow("LOCAL:0.1"), +    lastDepositFee: Amounts.parseOrThrow("LOCAL:0.1"), +  }); + +}); + +test("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 +  ); + +  expect(t, coins).deep.equal({ +    "hash0;32;http://exchange.localhost/": { +      exchangeBaseUrl: "http://exchange.localhost/", +      denomPubHash: "hash0", +      maxAge: 32, +      contributions: [ +        Amounts.parseOrThrow("LOCAL:9.9"), +        Amounts.parseOrThrow("LOCAL:9.9"), +        Amounts.parseOrThrow("LOCAL:0.2") +      ], +    } +  }); + +  expect(t, tally).deep.equal({ +    amountAcc: Amounts.parseOrThrow("LOCAL:20"), +    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") +  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 +  ); + +  expect(t, coins).deep.equal(undefined); + +  expect(t, tally).deep.equal({ +    amountAcc: Amounts.parseOrThrow("LOCAL:49.5"), +    depositFeesAcc: Amounts.parseOrThrow("LOCAL:0.5"), +    lastDepositFee: Amounts.parseOrThrow("LOCAL:0.1"), +  }); + +}); + + + + +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" +      }, +      "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, + +    } +  }) +} diff --git a/packages/taler-wallet-core/src/util/coinSelection.ts b/packages/taler-wallet-core/src/util/coinSelection.ts index 6fd0f1b86..0885215dd 100644 --- a/packages/taler-wallet-core/src/util/coinSelection.ts +++ b/packages/taler-wallet-core/src/util/coinSelection.ts @@ -894,6 +894,12 @@ interface PeerCoinSelectionTally {    lastDepositFee: AmountJson;  } +/** + * exporting for testing + */ +export function testing_greedySelectPeer(...args: Parameters<typeof greedySelectPeer>): ReturnType<typeof greedySelectPeer> { +  return greedySelectPeer(...args) +}  function greedySelectPeer(    candidates: AvailableDenom[],    instructedAmount: AmountLike, @@ -912,19 +918,19 @@ function greedySelectPeer(          instructedAmount,          tally.amountAcc,        ).amount; -      const coinSpend = Amounts.max( -        Amounts.min(amountPayRemaining, denom.value), -        denom.feeDeposit, -      ); +      const coinContrib = Amounts.sub(denom.value, denom.feeDeposit).amount + +      const coinSpend = Amounts.min(amountPayRemaining, coinContrib) +              tally.amountAcc = Amounts.add(tally.amountAcc, coinSpend).amount; -      // Since this is a peer payment, there is no merchant to -      // potentially cover the deposit fees. -      tally.amountAcc = Amounts.sub(tally.amountAcc, denom.feeDeposit).amount; +        tally.depositFeesAcc = Amounts.add(          tally.depositFeesAcc,          denom.feeDeposit,        ).amount; +        tally.lastDepositFee = Amounts.parseOrThrow(denom.feeDeposit); +              contributions.push(coinSpend);      }      if (contributions.length > 0) { | 
