coinsim.ts (4130B)
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 18 function getRandomInt(min, max) { 19 return Math.floor(Math.random() * (max - min + 1)) + min; 20 } 21 22 const denoms = [8096, 4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1]; 23 24 // mapping from denomination index to count 25 const wallet = denoms.map(() => 0); 26 27 const trans_max = 5000; 28 const trans_min = 4; 29 30 const withdraw_max = 10000; 31 32 const num_transactions = parseInt(process.argv[2]); 33 34 // Refresh or withdraw operations 35 let ops = 0; 36 let ops_refresh = 0; 37 let ops_withdraw = 0; 38 let ops_spend = 0; 39 let refresh_output = 0; 40 41 function withdraw(amount, is_refresh) { 42 while (amount != 0) { 43 for (let i = 0; i < denoms.length; i++) { 44 let d = denoms[i]; 45 if (d <= amount) { 46 amount -= d; 47 wallet[i]++; 48 ops++; 49 if (!is_refresh) { 50 ops_withdraw++; 51 } else { 52 refresh_output++; 53 } 54 break; 55 } 56 } 57 } 58 } 59 60 function spendSmallestFirst(cost) { 61 while (cost != 0) { 62 for (let j = 0; j < denoms.length; j++) { 63 const k = denoms.length - j - 1; 64 const d = denoms[k]; 65 const w = wallet[k]; 66 if (w == 0) { 67 continue; 68 } 69 if (d <= cost) { 70 // spend 71 wallet[k]--; 72 cost -= d; 73 ops++; 74 ops_spend++; 75 break; 76 } 77 // partially spend and then refresh 78 ops++; 79 ops_spend++; 80 let r = d - cost; 81 ops_refresh++; 82 wallet[k]--; 83 withdraw(r, true); 84 cost = 0; 85 } 86 } 87 } 88 89 function spendLargestFirst(cost) { 90 while (cost != 0) { 91 for (let j = 0; j < denoms.length; j++) { 92 const d = denoms[j]; 93 const w = wallet[j]; 94 if (w == 0) { 95 continue; 96 } 97 if (d <= cost) { 98 // spend 99 wallet[j]--; 100 cost -= d; 101 ops++; 102 ops_spend++; 103 break; 104 } 105 // partially spend and then refresh 106 ops++; 107 ops_spend++; 108 let r = d - cost; 109 ops_refresh++; 110 wallet[j]--; 111 withdraw(r, true); 112 cost = 0; 113 } 114 } 115 } 116 117 function spendHybrid(cost) { 118 for (let j = 0; j < denoms.length; j++) { 119 const k = denoms.length - j - 1; 120 const d = denoms[k]; 121 const w = wallet[k]; 122 if (w == 0) { 123 continue; 124 } 125 if (d < cost) { 126 continue; 127 } 128 // partially spend and then refresh 129 ops++; 130 ops_spend++; 131 let r = d - cost; 132 ops_refresh++; 133 wallet[k]--; 134 withdraw(r, true); 135 cost = 0; 136 } 137 138 spendSmallestFirst(cost); 139 } 140 141 for (let i = 0; i < num_transactions; i++) { 142 // check existing wallet balance 143 let balance = 0; 144 for (let j = 0; j < denoms.length; j++) { 145 balance += wallet[j] * denoms[j] 146 } 147 // choose how much we want to spend 148 let cost = getRandomInt(trans_min, trans_max); 149 if (balance < cost) { 150 // we need to withdraw 151 let amount = getRandomInt(cost - balance, withdraw_max); 152 withdraw(amount, false); 153 } 154 155 // check that we now have enough balance 156 balance = 0; 157 for (let j = 0; j < denoms.length; j++) { 158 balance += wallet[j] * denoms[j] 159 } 160 161 if (balance < cost) { 162 throw Error("not enough balance"); 163 } 164 165 // now we spend 166 spendHybrid(cost); 167 } 168 169 console.log("total ops", ops / num_transactions); 170 console.log("spend ops", ops_spend / num_transactions); 171 console.log("pure withdraw ops", ops_withdraw / num_transactions); 172 console.log("refresh (multi output) ops", ops_refresh / num_transactions); 173 console.log("refresh output", refresh_output / ops_refresh);