diff --git a/contrib/coinsim.ts b/contrib/coinsim.ts new file mode 100644 index 000000000..9b8e12d2e --- /dev/null +++ b/contrib/coinsim.ts @@ -0,0 +1,152 @@ +/* + This file is part of GNU Taler + (C) 2018 GNUnet e.V. + + 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 + */ + + +function getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +const denoms = [512, 256, 128, 64, 32, 16, 8, 4, 2, 1]; + +// mapping from denomination index to count +const wallet = denoms.map(() => 0); + +const trans_max = 1000; +const trans_min = 2; + +const withdraw_max = 5000; + +const num_transactions = parseInt(process.argv[2]); + +// Refresh or withdraw operations +let ops = 0; + +function withdraw(amount) { + while (amount != 0) { + for (let i = 0; i < denoms.length; i++) { + let d = denoms[i]; + if (d <= amount) { + amount -= d; + wallet[i]++; + ops++; + break; + } + } + } +} + +function spendSmallestFirst(cost) { + while (cost != 0) { + for (let j = 0; j < denoms.length; j++) { + const k = denoms.length - j - 1; + const d = denoms[k]; + const w = wallet[k]; + if (w == 0) { + continue; + } + if (d <= cost) { + // spend + wallet[k]--; + cost -= d; + ops++; + break; + } + // partially spend and then refresh + ops++; + let r = d - cost; + wallet[k]--; + withdraw(r); + cost = 0; + } + } +} + +function spendLargestFirst(cost) { + while (cost != 0) { + for (let j = 0; j < denoms.length; j++) { + const d = denoms[j]; + const w = wallet[j]; + if (w == 0) { + continue; + } + if (d <= cost) { + // spend + wallet[j]--; + cost -= d; + ops++; + break; + } + // partially spend and then refresh + ops++; + let r = d - cost; + wallet[j]--; + withdraw(r); + cost = 0; + } + } +} + +function spendHybrid(cost) { + for (let j = 0; j < denoms.length; j++) { + const k = denoms.length - j - 1; + const d = denoms[k]; + const w = wallet[k]; + if (w == 0) { + continue; + } + if (d < cost) { + continue; + } + // partially spend and then refresh + ops++; + let r = d - cost; + wallet[k]--; + withdraw(r); + cost = 0; + } + + spendSmallestFirst(cost); +} + +for (let i = 0; i < num_transactions; i++) { + // check existing wallet balance + let balance = 0; + for (let j = 0; j < denoms.length; j++) { + balance += wallet[j] * denoms[j] + } + // choose how much we want to spend + let cost = getRandomInt(trans_min, trans_max); + if (balance < cost) { + // we need to withdraw + let amount = getRandomInt(cost - balance, withdraw_max); + withdraw(amount); + } + + // check that we now have enough balance + balance = 0; + for (let j = 0; j < denoms.length; j++) { + balance += wallet[j] * denoms[j] + } + + if (balance < cost) { + throw Error("not enough balance"); + } + + // now we spend + spendHybrid(cost); +} + +console.log(ops / num_transactions);