├── README.md └── mk_genesis_block.py /README.md: -------------------------------------------------------------------------------- 1 | # Genesis block generator 2 | 3 | Run as follows: 4 | 5 | ```bash 6 | $ python mk_genesis_block.py --extradata > genesis.json 7 | ``` 8 | -------------------------------------------------------------------------------- /mk_genesis_block.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import json, re 3 | import random 4 | import sys 5 | import bitcoin as b 6 | import sys 7 | import json 8 | import os 9 | try: 10 | from urllib.request import build_opener 11 | except: 12 | from urllib2 import build_opener 13 | 14 | # Timestamp of sale start: midnight CET Jul 22 15 | start = 1406066400 16 | # Initial sale rate 17 | initial_rate = 2000 18 | # Initial sale rate duration 19 | initial_period = 14 * 86400 20 | # step size for declining rate phase 21 | rate_decline = 30 22 | # Length of step 23 | rate_period = 86400 24 | # Number of declining periods 25 | rate_periods = 22 26 | # Final rate 27 | final_rate = 1337 28 | # Period during which final rate is effective 29 | final_period = 6 * 86400 + 3600 # 1h of slack 30 | # Accept post-sale purchases? 31 | post_rate = 0 32 | # Exodus address 33 | exodus = '36PrZ1KHYMpqSyAQXSG8VwbUiq2EogxLo2' 34 | # Minimum satoshis accepted 35 | minimum = 1000000 36 | # Maximum satoshis accepted 37 | maximum = 150000000000 38 | 39 | # Create caches directory 40 | caches = {} 41 | 42 | # Foundation address 43 | foundation_address = '5abfec25f74cd88437631a7731906932776356f9' 44 | 45 | try: 46 | os.mkdir('caches') 47 | except: 48 | pass 49 | 50 | 51 | 52 | INSIGHT_ADDR = 'http://178.19.221.38:3000' 53 | 54 | 55 | # Makes a request to a given URL (first arg) and optional params (second arg) 56 | def make_request(*args): 57 | opener = build_opener() 58 | opener.addheaders = [('User-agent', 59 | 'Mozilla/5.0'+str(random.randrange(1000000)))] 60 | try: 61 | return opener.open(*args).read().strip() 62 | except Exception as e: 63 | try: 64 | p = e.read().strip() 65 | except: 66 | p = e 67 | raise Exception(p) 68 | 69 | 70 | # Grab history from an insight server (if desired) 71 | def insight_history(a): 72 | hashes = json.loads(make_request(INSIGHT_ADDR + '/api/addr/'+a))["transactions"] 73 | o = [] 74 | for i in range(0, len(hashes), 10): 75 | h = hashes[i:i+10] 76 | t = json.loads(make_request(INSIGHT_ADDR + '/api/multitx/'+','.join(h))) 77 | sys.stderr.write('Getting txs: %d\n' % i) 78 | if isinstance(t, dict): 79 | t = [t] 80 | for tee in t: 81 | for i, out in enumerate(tee["vout"]): 82 | if a in out["scriptPubKey"]["addresses"]: 83 | o.append({"output": tee["txid"]+':'+str(i), 84 | "block_height": tee["confirmedIn"], 85 | "value": out["valueSat"]}) 86 | return o 87 | 88 | 89 | # Grab a block timestamp from an insight server (if desired) 90 | def insight_get_block_timestamp(a): 91 | addrtail = ','.join([str(x) for x in a]) if isinstance(a, list) else str(a) 92 | o = json.loads(make_request(INSIGHT_ADDR + '/api/blockheader-by-index/'+addrtail)) 93 | if isinstance(o, list): 94 | return [x['time'] for x in o] 95 | else: 96 | return o['time'] 97 | 98 | 99 | # Fetch a transaction from an insight server (if desired) 100 | def insight_fetchtx(a): 101 | addrtail = ','.join(a) if isinstance(a, list) else a 102 | return json.loads(make_request(INSIGHT_ADDR + '/api/rawmultitx/'+addrtail)) 103 | 104 | # Get our network data grabbing methods either from BCI/blockr or from insight, 105 | # depending on which one the user prefers. Use --insight to use insight or 106 | # --insight 1.2.3.4:30303 to use one's own insight server (need the custom 107 | # batch-query-compatible version from http://github.com/vbuterin/insight-api ) 108 | if '--insight' in sys.argv: 109 | ipport = (sys.argv+[None])[sys.argv.index('--insight') + 1] 110 | if ipport: 111 | INSIGHT_ADDR = 'http://'+ipport 112 | _fetchtx = insight_fetchtx 113 | _history = insight_history 114 | _get_block_timestamp = insight_get_block_timestamp 115 | else: 116 | _fetchtx = b.blockr_fetchtx 117 | _history = b.history 118 | _get_block_timestamp = b.get_block_timestamp 119 | 120 | 121 | # Grab the extra data command line argument 122 | if '--extradata' in sys.argv: 123 | d = (sys.argv+[None])[sys.argv.index('--extradata') + 1] 124 | EXTRADATA = (d[2:] if d[:2] == '0x' else d).decode('hex') 125 | else: 126 | EXTRADATA = '' 127 | 128 | 129 | # Cache methods that get networking data. Important since this script takes 130 | # a very long time, and will almost certainly be interrupted multiple times 131 | # while in progress 132 | def cache_method_factory(method, filename): 133 | def new_method(arg): 134 | if filename not in caches: 135 | try: 136 | caches[filename] = json.load(open(filename, 'r')) 137 | except: 138 | caches[filename] = {} 139 | c = caches[filename] 140 | if str(arg) not in c: 141 | # Try to get the result five times 142 | have_problem = False 143 | for tried in range(4): 144 | try: 145 | c[str(arg)] = method(arg) 146 | tried = 'done' 147 | break 148 | except Exception: 149 | sys.stderr.write('API not returning data. Retrying\n') 150 | have_problem = True 151 | pass 152 | if tried != 'done': 153 | c[str(arg)] = method(arg) 154 | elif have_problem: 155 | sys.stderr.write('Data received, all good\n') 156 | json.dump(c, open(filename, 'w')) 157 | return c[str(arg)] 158 | return new_method 159 | 160 | # Cached versions of the BCI/blockr or insight methods that we need 161 | get_block_timestamp = cache_method_factory(_get_block_timestamp, 162 | 'caches/blocktimestamps.json') 163 | fetchtx = cache_method_factory(_fetchtx, 'caches/fetchtx.json') 164 | history = cache_method_factory(_history, 'caches/history.json') 165 | 166 | 167 | # Get a dictionary of the transactions and block heights, taking as input 168 | # a history produced by pybitcointools 169 | def get_txs_and_heights(outs): 170 | txs = {} 171 | heights = {} 172 | for i in range(0, len(outs), 20): 173 | txhashes = [] 174 | fetched_heights = [] 175 | for j in range(i, min(i + 20, len(outs))): 176 | if outs[j]['output'][65:] == '0': 177 | txhashes.append(outs[j]['output'][:64]) 178 | fetched_heights.append(outs[j]['block_height']) 179 | else: 180 | sys.stderr.write("Non-purchase tx found (genesis output index not zero): %s\n" % 181 | outs[j]['output'][:64]) 182 | fetched_txs = fetchtx(txhashes) 183 | assert len(fetched_txs) == len(txhashes) == len(fetched_heights) 184 | for h, tx, ht in zip(txhashes, fetched_txs, fetched_heights): 185 | assert b.txhash(str(tx)) == h 186 | txs[h] = tx 187 | heights[h] = ht 188 | sys.stderr.write('Processed transactions: %d\n' % len(txs)) 189 | return {"txs": txs, "heights": heights} 190 | 191 | 192 | # Produce a json list of purchases, taking as input a dictionary of 193 | # transactions and heights 194 | def list_purchases(obj): 195 | txs, heights = obj['txs'], obj['heights'] 196 | process_queue = [] 197 | for h in txs: 198 | txhex = str(txs[h]) 199 | txouts = b.deserialize(txhex)['outs'] 200 | if len(txouts) >= 2 and txouts[0]['value'] >= minimum - 30000: 201 | addr = b.script_to_address(txouts[0]['script']) 202 | if addr == exodus: 203 | v = txouts[0]['value'] + 30000 204 | process_queue.append({ 205 | "tx": h, 206 | "addr": b.b58check_to_hex(b.script_to_address( 207 | txouts[1]['script'])), 208 | "value": v, 209 | "height": heights[h] 210 | }) 211 | else: 212 | sys.stderr.write("Non-purchase tx found (not to exodus): %s\n" % h) 213 | elif len(txouts) == 1: 214 | sys.stderr.write("Non-purchase tx found (single output): %s\n" % h) 215 | else: 216 | sys.stderr.write("Non-purchase tx found (insufficient value): %s\n" % h) 217 | sys.stderr.write('Gathered outputs, collecting block timestamps\n') 218 | # Determine the timestamp for every block height. We care about 219 | # the timestamp of the previous confirmed block before a transaction. 220 | # Save the results as a dictionary of transaction data 221 | o = [] 222 | for i in range(0, len(process_queue), 20): 223 | subpq = process_queue[i:i+20] 224 | t = get_block_timestamp([x['height'] - 1 for x in subpq]) 225 | assert len(t) == len(subpq), [x['height'] - 1 for x in subpq] 226 | o.extend([{ 227 | "tx": _a["tx"], 228 | "addr": _a["addr"], 229 | "value": _a["value"], 230 | "time": _b 231 | } for _a, _b in zip(subpq, t)]) 232 | sys.stderr.write('Collected timestamps: %d\n' % len(o)) 233 | return o 234 | 235 | 236 | # Compute ether value from BTC value, using as input objects containing 237 | # ether address, value and time and saving a map of ether address => balance 238 | def evaluate_purchases(purchases): 239 | balances = {} 240 | for p in purchases: 241 | if p["time"] < start + initial_period: 242 | rate = initial_rate 243 | elif p["time"] < start + initial_period + rate_period * rate_periods: 244 | pid = (p["time"] - (start + initial_period)) // rate_period + 1 245 | rate = initial_rate - rate_decline * pid 246 | elif p["time"] < start + initial_period + rate_period * \ 247 | rate_periods + final_period: 248 | rate = final_rate 249 | else: 250 | rate = post_rate 251 | # Round to the nearest finney 252 | balance_to_add = (p["value"] * rate // 10**5) * 10**15 253 | balances[p["addr"]] = balances.get(p["addr"], 0) + balance_to_add 254 | return {k: balances[k] for k in sorted(balances.keys())} 255 | 256 | 257 | # Compute a genesis block from purchase balances 258 | def mk_genesis_block(balances): 259 | o = {k: {"balance": str(v)} for k, v in balances.items()} 260 | total_purchased = sum(balances.values()) 261 | o[foundation_address] = { 262 | "balance": str(total_purchased * 198 // 1000) 263 | } 264 | sys.stderr.write("Finished, total purchased: %d\n" % total_purchased) 265 | sys.stderr.write("Foundation wallet creator address: %s\n" % foundation_address) 266 | sys.stderr.write("Foundation balance: %s\n" % (total_purchased * 198 // 1000)) 267 | return { 268 | "nonce": "0x0000000000000042", 269 | "timestamp": "0x00", 270 | "difficulty": "0x400000000", 271 | "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", 272 | "extraData": "0x"+EXTRADATA.encode('hex'), 273 | "gasLimit": "0x1388", 274 | "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", 275 | "coinbase": "0x0000000000000000000000000000000000000000", 276 | "alloc": o 277 | } 278 | 279 | 280 | def evaluate(): 281 | outs = history(exodus) 282 | sys.stderr.write('Gathered history: %d\n' % len(outs)) 283 | th = get_txs_and_heights(outs) 284 | sys.stderr.write('Gathered txs and heights\n') 285 | p = list_purchases(th) 286 | sys.stderr.write('Listed purchases\n') 287 | o = evaluate_purchases(p) 288 | sys.stderr.write('Computed purchases\n') 289 | g = mk_genesis_block(o) 290 | return g 291 | 292 | if __name__ == '__main__': 293 | print json.dumps(evaluate(), indent=4) 294 | --------------------------------------------------------------------------------