coinsim.ts (3559B)
1 /* 2 This file is part of GNU Taler 3 (C) 2018 GNUnet e.V. 4 5 GNU Taler is free software; you can redistribute it and/or modify it under the 6 terms of the GNU General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 17 function getRandomInt(min, max) { 18 return Math.floor(Math.random() * (max - min + 1)) + min; 19 } 20 21 const denoms = [8096, 4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1]; 22 23 // mapping from denomination index to count 24 const wallet = denoms.map(() => 0); 25 26 const trans_max = 5000; 27 const trans_min = 4; 28 29 const withdraw_max = 10000; 30 31 const num_transactions = parseInt(process.argv[2]); 32 33 // Refresh or withdraw operations 34 let ops = 0; 35 let ops_refresh = 0; 36 let ops_withdraw = 0; 37 let ops_spend = 0; 38 let refresh_output = 0; 39 40 function withdraw(amount, is_refresh) { 41 while (amount != 0) { 42 for (let i = 0; i < denoms.length; i++) { 43 let d = denoms[i]; 44 if (d <= amount) { 45 amount -= d; 46 wallet[i]++; 47 ops++; 48 if (!is_refresh) { 49 ops_withdraw++; 50 } else { 51 refresh_output++; 52 } 53 break; 54 } 55 } 56 } 57 } 58 59 function spendSmallestFirst(cost) { 60 while (cost != 0) { 61 for (let j = 0; j < denoms.length; j++) { 62 const k = denoms.length - j - 1; 63 const d = denoms[k]; 64 const w = wallet[k]; 65 if (w == 0) { 66 continue; 67 } 68 if (d <= cost) { 69 // spend 70 wallet[k]--; 71 cost -= d; 72 ops++; 73 ops_spend++; 74 break; 75 } 76 // partially spend and then refresh 77 ops++; 78 ops_spend++; 79 let r = d - cost; 80 ops_refresh++; 81 wallet[k]--; 82 withdraw(r, true); 83 cost = 0; 84 } 85 } 86 } 87 88 function spendHybrid(cost) { 89 for (let j = 0; j < denoms.length; j++) { 90 const k = denoms.length - j - 1; 91 const d = denoms[k]; 92 const w = wallet[k]; 93 if (w == 0) { 94 continue; 95 } 96 if (d < cost) { 97 continue; 98 } 99 // partially spend and then refresh 100 ops++; 101 ops_spend++; 102 let r = d - cost; 103 ops_refresh++; 104 wallet[k]--; 105 withdraw(r, true); 106 cost = 0; 107 } 108 109 spendSmallestFirst(cost); 110 } 111 112 for (let i = 0; i < num_transactions; i++) { 113 // check existing wallet balance 114 let balance = 0; 115 for (let j = 0; j < denoms.length; j++) { 116 balance += wallet[j] * denoms[j]; 117 } 118 // choose how much we want to spend 119 let cost = getRandomInt(trans_min, trans_max); 120 if (balance < cost) { 121 // we need to withdraw 122 let amount = getRandomInt(cost - balance, withdraw_max); 123 withdraw(amount, false); 124 } 125 126 // check that we now have enough balance 127 balance = 0; 128 for (let j = 0; j < denoms.length; j++) { 129 balance += wallet[j] * denoms[j]; 130 } 131 132 if (balance < cost) { 133 throw Error("not enough balance"); 134 } 135 136 // now we spend 137 spendHybrid(cost); 138 } 139 140 console.log("total ops", ops / num_transactions); 141 console.log("spend ops", ops_spend / num_transactions); 142 console.log("pure withdraw ops", ops_withdraw / num_transactions); 143 console.log("refresh (multi output) ops", ops_refresh / num_transactions); 144 console.log("refresh output", refresh_output / ops_refresh);